diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ddffaf9f..10f9d0084 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,7 +51,7 @@ repos: - 'prettier-plugin-organize-imports@4.1.0' # Python hooks - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.3 + rev: v0.9.4 hooks: - id: ruff - id: ruff-format diff --git a/.ruff.toml b/.ruff.toml index 96ee7430b..a29b471c5 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -32,13 +32,13 @@ extend-select = [ "RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf "FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly "PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt ] ignore = ["DJ001", "SIM105", "RUF012"] [lint.per-file-ignores] ".github/scripts/*.py" = ["E501", "INP001", "SIM117"] "docker/wait-for-redis.py" = ["INP001", "T201"] -"src/documents/consumer.py" = ["PTH"] # TODO Enable & remove "src/documents/file_handling.py" = ["PTH"] # TODO Enable & remove "src/documents/management/commands/document_consumer.py" = ["PTH"] # TODO Enable & remove "src/documents/management/commands/document_exporter.py" = ["PTH"] # TODO Enable & remove @@ -51,8 +51,6 @@ ignore = ["DJ001", "SIM105", "RUF012"] "src/documents/signals/handlers.py" = ["PTH"] # TODO Enable & remove "src/documents/tasks.py" = ["PTH"] # TODO Enable & remove "src/documents/tests/test_api_app_config.py" = ["PTH"] # TODO Enable & remove -"src/documents/tests/test_api_bulk_download.py" = ["PTH"] # TODO Enable & remove -"src/documents/tests/test_api_documents.py" = ["PTH"] # TODO Enable & remove "src/documents/tests/test_classifier.py" = ["PTH"] # TODO Enable & remove "src/documents/tests/test_consumer.py" = ["PTH"] # TODO Enable & remove "src/documents/tests/test_file_handling.py" = ["PTH"] # TODO Enable & remove diff --git a/Dockerfile b/Dockerfile index d3be53dff..2284e9f8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -127,32 +127,21 @@ RUN set -eux \ && apt-get update \ && apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES} \ && echo "Installing pre-built updates" \ + && curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \ + https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \ + https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \ + https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ + https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ + https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \ + https://github.com/paperless-ngx/builder/releases/download/jbig2enc-${JBIG2ENC_VERSION}/jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \ && echo "Installing qpdf ${QPDF_VERSION}" \ - && curl --fail --silent --show-error --location \ - --output libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \ - https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \ - && curl --fail --silent --show-error --location \ - --output qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \ - https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \ && dpkg --install ./libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \ && dpkg --install ./qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \ && echo "Installing Ghostscript ${GS_VERSION}" \ - && curl --fail --silent --show-error --location \ - --output libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ - https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ - && curl --fail --silent --show-error --location \ - --output ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ - https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ - && curl --fail --silent --show-error --location \ - --output libgs10-common_${GS_VERSION}.dfsg-1_all.deb \ - https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \ && dpkg --install ./libgs10-common_${GS_VERSION}.dfsg-1_all.deb \ && dpkg --install ./libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ && dpkg --install ./ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ && echo "Installing jbig2enc" \ - && curl --fail --silent --show-error --location \ - --output jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \ - https://github.com/paperless-ngx/builder/releases/download/jbig2enc-${JBIG2ENC_VERSION}/jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \ && dpkg --install ./jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \ && echo "Cleaning up image layer" \ && rm --force --verbose *.deb \ @@ -232,12 +221,11 @@ RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \ && apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \ && python3 -m pip install --no-cache-dir --upgrade wheel \ && echo "Installing Python requirements" \ - && curl --fail --silent --show-error --location \ - --output psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl \ - https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl \ - && curl --fail --silent --show-error --location \ - --output psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl \ - https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl \ + && curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \ + https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl \ + https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl \ + https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_aarch64.whl \ + https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl \ && python3 -m pip install --default-timeout=1000 --find-links . --requirement requirements.txt \ && echo "Installing NLTK data" \ && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \ diff --git a/Pipfile b/Pipfile index 0e2486b03..1e69f316d 100644 --- a/Pipfile +++ b/Pipfile @@ -58,7 +58,7 @@ uvicorn = {extras = ["standard"], version = "==0.25.0"} watchdog = "~=6.0" whitenoise = "~=6.8" whoosh = "~=2.7" -zxing-cpp = {version = "*", platform_machine = "== 'x86_64'"} +zxing-cpp = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index de4290d84..1f74e6708 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3806c1dbfde8e9383e748c106c217170d6dcdbb8b95d573030b2294dab32d462" + "sha256": "6a7869231917d0cf6f5852520b5cb9b0df3802ed162b1a8107d0b1e1c37f0535" }, "pipfile-spec": 6, "requires": {}, @@ -589,12 +589,12 @@ }, "django-soft-delete": { "hashes": [ - "sha256:cc40398ccd869c75a6d6ba7f526e16c4afe2b0c0811c213a318d96bb4c58a787", - "sha256:fdaf2788d404930557f1300ce40bbd764f6938775a35a3175c66fe7778666093" + "sha256:603a29e82bbb7a5bada69f2754fad225ccd8cd7f485320ec06d0fc4e9dfddcf0", + "sha256:d2f9db449a4f008e9786f82fa4bafbe4075f7a0b3284844735007e988b2a4df6" ], "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==1.0.16" + "version": "==1.0.18" }, "djangorestframework": { "hashes": [ @@ -2264,7 +2264,7 @@ "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], - "markers": "python_version >= '3.8'", + "markers": "python_version < '3.11'", "version": "==4.12.2" }, "tzdata": { @@ -2791,7 +2791,6 @@ "sha256:fbd5b253ad0f8823c5c104feaaa19acab95c217cb924b012d55ff339c42b3583", "sha256:fd3f175f7b57cfbdea56afdb5335eaebaadeebc06e20a087d9aa3f99637c4aa5" ], - "markers": "platform_machine == 'x86_64'", "version": "==2.3.0" } }, @@ -2838,19 +2837,19 @@ }, "babel": { "hashes": [ - "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", - "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" + "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", + "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2" ], "markers": "python_version >= '3.8'", - "version": "==2.16.0" + "version": "==2.17.0" }, "certifi": { "hashes": [ - "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", - "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2024.12.14" + "version": "==2025.1.31" }, "cffi": { "hashes": [ @@ -3303,7 +3302,6 @@ "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" ], - "index": "pypi", "markers": "python_version >= '3.7'", "version": "==3.1.5" }, @@ -3416,12 +3414,12 @@ }, "mkdocs-material": { "hashes": [ - "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825", - "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385" + "sha256:71d90dbd63b393ad11a4d90151dfe3dcbfcd802c0f29ce80bebd9bbac6abc753", + "sha256:a3de1c5d4c745f10afa78b1a02f917b9dce0808fb206adc0f5bb48b58c1ca21f" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==9.5.50" + "version": "==9.6.2" }, "mkdocs-material-extensions": { "hashes": [ @@ -3659,11 +3657,11 @@ }, "pymdown-extensions": { "hashes": [ - "sha256:637951cbfbe9874ba28134fb3ce4b8bcadd6aca89ac4998ec29dcbafd554ae08", - "sha256:b65801996a0cd4f42a3110810c306c45b7313c09b0610a6f773730f2a9e3c96b" + "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", + "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b" ], "markers": "python_version >= '3.8'", - "version": "==10.14.1" + "version": "==10.14.3" }, "pyopenssl": { "hashes": [ @@ -3758,8 +3756,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0.post0" }, "pywavelets": { @@ -3983,28 +3980,28 @@ }, "ruff": { "hashes": [ - "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2", - "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4", - "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439", - "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730", - "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4", - "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5", - "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624", - "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b", - "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a", - "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b", - "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5", - "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4", - "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c", - "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519", - "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1", - "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4", - "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6", - "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c" + "sha256:05bebf4cdbe3ef75430d26c375773978950bbf4ee3c95ccb5448940dc092408e", + "sha256:1d4c8772670aecf037d1bf7a07c39106574d143b26cfe5ed1787d2f31e800214", + "sha256:37c892540108314a6f01f105040b5106aeb829fa5fb0561d2dcaf71485021137", + "sha256:433dedf6ddfdec7f1ac7575ec1eb9844fa60c4c8c2f8887a070672b8d353d34c", + "sha256:54499fb08408e32b57360f6f9de7157a5fec24ad79cb3f42ef2c3f3f728dfe2b", + "sha256:56acd6c694da3695a7461cc55775f3a409c3815ac467279dfa126061d84b314b", + "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41", + "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706", + "sha256:6907ee3529244bb0ed066683e075f09285b38dd5b4039370df6ff06041ca19e7", + "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf", + "sha256:87c90c32357c74f11deb7fbb065126d91771b207bf9bfaaee01277ca59b574ec", + "sha256:a6c634fc6f5a0ceae1ab3e13c58183978185d131a29c425e4eaa9f40afe1e6d6", + "sha256:bfc5f1d7afeda8d5d37660eeca6d389b142d7f2b5a1ab659d9214ebd0e025231", + "sha256:d612dbd0f3a919a8cc1d12037168bfa536862066808960e0cc901404b77968f0", + "sha256:db1192ddda2200671f9ef61d9597fcef89d934f5d1705e571a93a67fb13a4402", + "sha256:de9edf2ce4b9ddf43fd93e20ef635a900e25f622f87ed6e3047a664d0e8f810e", + "sha256:e0c93e7d47ed951b9394cf352d6695b31498e68fd5782d6cbc282425655f687a", + "sha256:faa935fc00ae854d8b638c16a5f1ce881bc3f67446957dd6f2af440a5fc8526b" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.9.3" + "version": "==0.9.4" }, "scipy": { "hashes": [ @@ -4073,7 +4070,7 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.17.0" }, "sniffio": { @@ -4206,7 +4203,6 @@ "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==6.0.0" }, diff --git a/docs/changelog.md b/docs/changelog.md index e2d2b98ac..56316942c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,28 @@ # Changelog +## paperless-ngx 2.14.7 + +### Features + +- Enhancement: require totp code for obtain auth token by [@shamoon](https://github.com/shamoon) [#8936](https://github.com/paperless-ngx/paperless-ngx/pull/8936) + +### Bug Fixes + +- Enhancement: require totp code for obtain auth token by [@shamoon](https://github.com/shamoon) [#8936](https://github.com/paperless-ngx/paperless-ngx/pull/8936) +- Fix: reflect doc links in bulk modify custom fields by [@shamoon](https://github.com/shamoon) [#8962](https://github.com/paperless-ngx/paperless-ngx/pull/8962) +- Fix: also ensure symmetric doc link removal on bulk edit by [@shamoon](https://github.com/shamoon) [#8963](https://github.com/paperless-ngx/paperless-ngx/pull/8963) + +### All App Changes + +
+4 changes + +- Chore(deps-dev): Bump ruff from 0.9.2 to 0.9.3 in the development group by @[dependabot[bot]](https://github.com/apps/dependabot) [#8928](https://github.com/paperless-ngx/paperless-ngx/pull/8928) +- Enhancement: require totp code for obtain auth token by [@shamoon](https://github.com/shamoon) [#8936](https://github.com/paperless-ngx/paperless-ngx/pull/8936) +- Fix: reflect doc links in bulk modify custom fields by [@shamoon](https://github.com/shamoon) [#8962](https://github.com/paperless-ngx/paperless-ngx/pull/8962) +- Fix: also ensure symmetric doc link removal on bulk edit by [@shamoon](https://github.com/shamoon) [#8963](https://github.com/paperless-ngx/paperless-ngx/pull/8963) +
+ ## paperless-ngx 2.14.6 ### Bug Fixes diff --git a/docs/configuration.md b/docs/configuration.md index c34c3abf8..b81c10b0d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1085,8 +1085,6 @@ or hidden folders some tools use to store data. If you have problems that your Barcodes/QR-Codes are not detected (especially with bad scan quality and/or small codes), try the other one. - zxing is not available on all platforms. - #### [`PAPERLESS_PRE_CONSUME_SCRIPT=`](#PAPERLESS_PRE_CONSUME_SCRIPT) {#PAPERLESS_PRE_CONSUME_SCRIPT} : After some initial validation, Paperless can trigger an arbitrary diff --git a/src-ui/e2e/document-list/document-list.spec.ts b/src-ui/e2e/document-list/document-list.spec.ts index 7719873d3..cd1a4c54e 100644 --- a/src-ui/e2e/document-list/document-list.spec.ts +++ b/src-ui/e2e/document-list/document-list.spec.ts @@ -83,9 +83,9 @@ test('date filtering', async ({ page }) => { await page.routeFromHAR(REQUESTS_HAR3, { notFound: 'fallback' }) await page.goto('/documents') await page.getByRole('button', { name: 'Dates' }).click() - await page.getByRole('menuitem', { name: 'Last 3 months' }).first().click() + await page.getByRole('menuitem', { name: 'Within 3 months' }).first().click() await expect(page.locator('pngx-document-list')).toHaveText(/one document/i) - await page.getByRole('menuitem', { name: 'Last 3 months' }).first().click() + await page.getByRole('menuitem', { name: 'Within 3 months' }).first().click() await page.getByLabel('Datesselected').getByRole('button').first().click() await page.getByRole('combobox', { name: 'Select month' }).selectOption('12') await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022') diff --git a/src-ui/e2e/document-list/requests/api-document-list3.har b/src-ui/e2e/document-list/requests/api-document-list3.har index 6d5d1808f..291915a65 100644 --- a/src-ui/e2e/document-list/requests/api-document-list3.har +++ b/src-ui/e2e/document-list/requests/api-document-list3.har @@ -3687,7 +3687,7 @@ "time": 1.501, "request": { "method": "GET", - "url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&created__date__gt=2022-12-11", + "url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&created__date__gte=2022-12-11", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [ @@ -3721,7 +3721,7 @@ "value": "true" }, { - "name": "created__date__gt", + "name": "created__date__gte", "value": "2022-12-11" } ], diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 5406b866b..34959e4a0 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -241,22 +241,22 @@ Document was added to Paperless-ngx. src/app/app.component.ts - 93 + 95 src/app/app.component.ts - 102 + 104 Open document src/app/app.component.ts - 95 + 97 src/app/components/admin/trash/trash.component.ts - 141 + 146 src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html @@ -279,29 +279,29 @@ Could not add : src/app/app.component.ts - 117 + 119 Document is being processed by Paperless-ngx. src/app/app.component.ts - 132 + 134 Dashboard src/app/app.component.ts - 139 + 141 src/app/components/app-frame/app-frame.component.html - 81 + 82 src/app/components/app-frame/app-frame.component.html - 83 + 84 src/app/components/dashboard/dashboard.component.html @@ -312,15 +312,15 @@ Documents src/app/app.component.ts - 150 + 152 src/app/components/app-frame/app-frame.component.html - 88 + 89 src/app/components/app-frame/app-frame.component.html - 90 + 91 src/app/components/document-list/document-list.component.ts @@ -332,26 +332,26 @@ src/app/components/manage/management-list/management-list.component.html - 101 + 102 src/app/components/manage/management-list/management-list.component.html - 101 + 102 src/app/components/manage/management-list/management-list.component.html - 101 + 102 src/app/components/manage/management-list/management-list.component.html - 101 + 102 Settings src/app/app.component.ts - 162 + 164 src/app/components/admin/settings/settings.component.html @@ -359,89 +359,89 @@ src/app/components/app-frame/app-frame.component.html - 50 + 51 src/app/components/app-frame/app-frame.component.html - 244 + 245 src/app/components/app-frame/app-frame.component.html - 246 + 247 Prev src/app/app.component.ts - 168 + 170 Next src/app/app.component.ts - 169 + 171 src/app/components/document-detail/document-detail.component.html - 95 + 100 End src/app/app.component.ts - 170 + 172 The dashboard can be used to show saved views, such as an 'Inbox'. Views are found under Manage > Saved Views once you have created some. src/app/app.component.ts - 176 + 178 Drag-and-drop documents here to start uploading or place them in the consume folder. You can also drag-and-drop documents anywhere on all other pages of the web app. Once you do, Paperless-ngx will start training its machine learning algorithms. src/app/app.component.ts - 183 + 185 The documents list shows all of your documents and allows for filtering as well as bulk-editing. There are three different view styles: list, small cards and large cards. A list of documents currently opened for editing is shown in the sidebar. src/app/app.component.ts - 188 + 190 The filtering tools allow you to quickly find documents using various searches, dates, tags, etc. src/app/app.component.ts - 195 + 197 Any combination of filters can be saved as a 'view' which can then be displayed on the dashboard and / or sidebar. src/app/app.component.ts - 201 + 203 Tags, correspondents, document types and storage paths can all be managed using these pages. They can also be created from the document edit view. src/app/app.component.ts - 206 + 208 Manage e-mail accounts and rules for automatically importing documents. src/app/app.component.ts - 214 + 216 src/app/components/manage/mail/mail.component.html @@ -452,14 +452,14 @@ Workflows give you more control over the document pipeline. src/app/app.component.ts - 222 + 224 File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process. src/app/app.component.ts - 230 + 232 src/app/components/admin/tasks/tasks.component.html @@ -470,28 +470,28 @@ Check out the settings for various tweaks to the web app. src/app/app.component.ts - 238 + 240 Thank you! 🙏 src/app/app.component.ts - 246 + 248 There are <em>tons</em> more features and info we didn't cover here, but this should get you started. Check out the documentation or visit the project on GitHub to learn more or to report issues. src/app/app.component.ts - 248 + 250 Lastly, on behalf of every contributor to this community-supported project, thank you for using Paperless-ngx! src/app/app.component.ts - 250 + 252 @@ -534,7 +534,7 @@ src/app/components/document-detail/document-detail.component.html - 348 + 353 @@ -545,7 +545,7 @@ src/app/components/admin/settings/settings.component.html - 349 + 364 src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html @@ -593,7 +593,7 @@ src/app/components/document-detail/document-detail.component.html - 341 + 346 src/app/components/document-list/bulk-editor/custom-fields-bulk-edit-dialog/custom-fields-bulk-edit-dialog.component.html @@ -658,11 +658,11 @@ src/app/components/app-frame/app-frame.component.html - 279 + 280 src/app/components/app-frame/app-frame.component.html - 282 + 283 @@ -739,7 +739,7 @@ src/app/components/document-detail/document-detail.component.html - 361 + 366 src/app/components/document-list/document-list.component.html @@ -944,60 +944,25 @@ 152 - - Document editing - - src/app/components/admin/settings/settings.component.html - 157 - - - - Use PDF viewer provided by the browser - - src/app/components/admin/settings/settings.component.html - 161 - - - - This is usually faster for displaying large PDF documents, but it might not work on some browsers. - - src/app/components/admin/settings/settings.component.html - 161 - - - - Automatically remove inbox tag(s) on save - - src/app/components/admin/settings/settings.component.html - 167 - - - - Show document thumbnail during loading - - src/app/components/admin/settings/settings.component.html - 173 - - Update checking src/app/components/admin/settings/settings.component.html - 178 + 157 Enable update checking src/app/components/admin/settings/settings.component.html - 181 + 160 What's this? src/app/components/admin/settings/settings.component.html - 182 + 161 src/app/components/common/page-header/page-header.component.html @@ -1012,97 +977,29 @@ Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. src/app/components/admin/settings/settings.component.html - 186,188 + 165,167 No tracking data is collected by the app in any way. src/app/components/admin/settings/settings.component.html - 190 - - - - Bulk editing - - src/app/components/admin/settings/settings.component.html - 196 - - - - Show confirmation dialogs - - src/app/components/admin/settings/settings.component.html - 199 - - - - Apply on close - - src/app/components/admin/settings/settings.component.html - 200 - - - - Global search - - src/app/components/admin/settings/settings.component.html - 204 - - - src/app/components/app-frame/global-search/global-search.component.ts - 120 - - - - Do not include advanced search results - - src/app/components/admin/settings/settings.component.html - 207 - - - - Full search links to - - src/app/components/admin/settings/settings.component.html - 215 - - - - Title and content search - - src/app/components/admin/settings/settings.component.html - 219 - - - - Advanced search - - src/app/components/admin/settings/settings.component.html - 220 - - - src/app/components/app-frame/global-search/global-search.component.html - 24 - - - src/app/components/document-list/filter-editor/filter-editor.component.ts - 166 + 169 Saved Views src/app/components/admin/settings/settings.component.html - 227 + 175 src/app/components/app-frame/app-frame.component.html - 204 + 205 src/app/components/app-frame/app-frame.component.html - 206 + 207 src/app/components/manage/saved-views/saved-views.component.html @@ -1113,14 +1010,77 @@ Show warning when closing saved views with unsaved changes src/app/components/admin/settings/settings.component.html - 230 + 178 + + + + Document editing + + src/app/components/admin/settings/settings.component.html + 184 + + + + Use PDF viewer provided by the browser + + src/app/components/admin/settings/settings.component.html + 188 + + + + This is usually faster for displaying large PDF documents, but it might not work on some browsers. + + src/app/components/admin/settings/settings.component.html + 188 + + + + Default zoom: + + src/app/components/admin/settings/settings.component.html + 194 + + + + Fit width + + src/app/components/admin/settings/settings.component.html + 198 + + + + Fit page + + src/app/components/admin/settings/settings.component.html + 199 + + + + Only applies to the Paperless-ngx PDF viewer. + + src/app/components/admin/settings/settings.component.html + 201 + + + + Automatically remove inbox tag(s) on save + + src/app/components/admin/settings/settings.component.html + 207 + + + + Show document thumbnail during loading + + src/app/components/admin/settings/settings.component.html + 213 Notes src/app/components/admin/settings/settings.component.html - 234 + 217 src/app/components/document-list/document-list.component.html @@ -1139,14 +1099,82 @@ Enable notes src/app/components/admin/settings/settings.component.html - 237 + 220 + + + + Bulk editing + + src/app/components/admin/settings/settings.component.html + 224 + + + + Show confirmation dialogs + + src/app/components/admin/settings/settings.component.html + 227 + + + + Apply on close + + src/app/components/admin/settings/settings.component.html + 228 + + + + Global search + + src/app/components/admin/settings/settings.component.html + 232 + + + src/app/components/app-frame/global-search/global-search.component.ts + 120 + + + + Do not include advanced search results + + src/app/components/admin/settings/settings.component.html + 235 + + + + Full search links to + + src/app/components/admin/settings/settings.component.html + 243 + + + + Title and content search + + src/app/components/admin/settings/settings.component.html + 247 + + + + Advanced search + + src/app/components/admin/settings/settings.component.html + 248 + + + src/app/components/app-frame/global-search/global-search.component.html + 24 + + + src/app/components/document-list/filter-editor/filter-editor.component.ts + 170 Permissions src/app/components/admin/settings/settings.component.html - 247 + 262 src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html @@ -1162,7 +1190,7 @@ src/app/components/document-detail/document-detail.component.html - 317 + 322 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -1209,28 +1237,28 @@ Default Permissions src/app/components/admin/settings/settings.component.html - 250 + 265 Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI src/app/components/admin/settings/settings.component.html - 254,256 + 269,271 Default Owner src/app/components/admin/settings/settings.component.html - 261 + 276 Objects without an owner can be viewed and edited by all users src/app/components/admin/settings/settings.component.html - 265 + 280 src/app/components/common/input/permissions/permissions-form/permissions-form.component.html @@ -1241,18 +1269,18 @@ Default View Permissions src/app/components/admin/settings/settings.component.html - 270 + 285 Users: src/app/components/admin/settings/settings.component.html - 275 + 290 src/app/components/admin/settings/settings.component.html - 302 + 317 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -1283,11 +1311,11 @@ Groups: src/app/components/admin/settings/settings.component.html - 285 + 300 src/app/components/admin/settings/settings.component.html - 312 + 327 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -1318,14 +1346,14 @@ Default Edit Permissions src/app/components/admin/settings/settings.component.html - 297 + 312 Edit permissions also grant viewing permissions src/app/components/admin/settings/settings.component.html - 321 + 336 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -1344,56 +1372,60 @@ Notifications src/app/components/admin/settings/settings.component.html - 329 + 344 + + + src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html + 11 Document processing src/app/components/admin/settings/settings.component.html - 332 + 347 Show notifications when new documents are detected src/app/components/admin/settings/settings.component.html - 336 + 351 Show notifications when document processing completes successfully src/app/components/admin/settings/settings.component.html - 337 + 352 Show notifications when document processing fails src/app/components/admin/settings/settings.component.html - 338 + 353 Suppress notifications on dashboard src/app/components/admin/settings/settings.component.html - 339 + 354 This will suppress all messages about document processing status on the dashboard. src/app/components/admin/settings/settings.component.html - 339 + 354 Cancel src/app/components/admin/settings/settings.component.html - 350 + 365 src/app/components/common/confirm-dialog/confirm-dialog.component.ts @@ -1468,21 +1500,21 @@ Use system language src/app/components/admin/settings/settings.component.ts - 75 + 76 Use date format of display language src/app/components/admin/settings/settings.component.ts - 78 + 79 Error retrieving users src/app/components/admin/settings/settings.component.ts - 213 + 217 src/app/components/admin/users-groups/users-groups.component.ts @@ -1493,7 +1525,7 @@ Error retrieving groups src/app/components/admin/settings/settings.component.ts - 232 + 236 src/app/components/admin/users-groups/users-groups.component.ts @@ -1504,32 +1536,32 @@ Settings were saved successfully. src/app/components/admin/settings/settings.component.ts - 521 + 532 Settings were saved successfully. Reload is required to apply some changes. src/app/components/admin/settings/settings.component.ts - 525 + 536 Reload now src/app/components/admin/settings/settings.component.ts - 526 + 537 An error occurred while saving settings. src/app/components/admin/settings/settings.component.ts - 536 + 547 src/app/components/app-frame/app-frame.component.ts - 159 + 161 @@ -1540,11 +1572,11 @@ src/app/components/app-frame/app-frame.component.html - 267 + 268 src/app/components/app-frame/app-frame.component.html - 269 + 270 @@ -1757,7 +1789,7 @@ src/app/components/document-detail/document-detail.component.html - 45 + 50 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -1938,11 +1970,11 @@ src/app/components/app-frame/app-frame.component.html - 227 + 228 src/app/components/app-frame/app-frame.component.html - 230 + 231 @@ -2014,7 +2046,7 @@ src/app/components/admin/trash/trash.component.ts - 111 + 116 src/app/components/admin/users-groups/users-groups.component.html @@ -2098,35 +2130,35 @@ src/app/components/manage/management-list/management-list.component.html - 83 + 84 src/app/components/manage/management-list/management-list.component.html - 83 + 84 src/app/components/manage/management-list/management-list.component.html - 83 + 84 src/app/components/manage/management-list/management-list.component.html - 83 + 84 src/app/components/manage/management-list/management-list.component.html - 95 + 96 src/app/components/manage/management-list/management-list.component.html - 95 + 96 src/app/components/manage/management-list/management-list.component.html - 95 + 96 src/app/components/manage/management-list/management-list.component.html - 95 + 96 src/app/components/manage/management-list/management-list.component.ts @@ -2160,7 +2192,7 @@ src/app/components/admin/trash/trash.component.ts - 105 + 110 src/app/components/manage/management-list/management-list.component.ts @@ -2186,7 +2218,7 @@ src/app/components/admin/trash/trash.component.ts - 109 + 114 src/app/components/admin/users-groups/users-groups.component.ts @@ -2194,7 +2226,7 @@ src/app/components/admin/users-groups/users-groups.component.ts - 174 + 177 src/app/components/manage/custom-fields/custom-fields.component.ts @@ -2206,7 +2238,7 @@ src/app/components/manage/mail/mail.component.ts - 286 + 296 src/app/components/manage/management-list/management-list.component.ts @@ -2217,78 +2249,74 @@ 137 - - Document deleted + + Document "" deleted src/app/components/admin/trash/trash.component.ts - 89 + 90 - - Error deleting document + + Error deleting document "" src/app/components/admin/trash/trash.component.ts - 94 - - - src/app/components/document-detail/document-detail.component.ts - 924 + 97 This operation will permanently delete the selected documents. src/app/components/admin/trash/trash.component.ts - 107 + 112 This operation will permanently delete all documents in the trash. src/app/components/admin/trash/trash.component.ts - 108 + 113 Document(s) deleted src/app/components/admin/trash/trash.component.ts - 119 + 124 Error deleting document(s) src/app/components/admin/trash/trash.component.ts - 126 + 131 - - Document restored + + Document "" restored src/app/components/admin/trash/trash.component.ts - 139 + 144 - - Error restoring document + + Error restoring document "" src/app/components/admin/trash/trash.component.ts - 149 + 155 Document(s) restored src/app/components/admin/trash/trash.component.ts - 159 + 167 Error restoring document(s) src/app/components/admin/trash/trash.component.ts - 165 + 173 @@ -2299,11 +2327,11 @@ src/app/components/app-frame/app-frame.component.html - 258 + 259 src/app/components/app-frame/app-frame.component.html - 260 + 261 @@ -2405,35 +2433,35 @@ src/app/components/manage/management-list/management-list.component.html - 82 + 83 src/app/components/manage/management-list/management-list.component.html - 82 + 83 src/app/components/manage/management-list/management-list.component.html - 82 + 83 src/app/components/manage/management-list/management-list.component.html - 82 + 83 src/app/components/manage/management-list/management-list.component.html - 92 + 93 src/app/components/manage/management-list/management-list.component.html - 92 + 93 src/app/components/manage/management-list/management-list.component.html - 92 + 93 src/app/components/manage/management-list/management-list.component.html - 92 + 93 src/app/components/manage/workflows/workflows.component.html @@ -2505,35 +2533,35 @@ src/app/components/admin/users-groups/users-groups.component.ts - 176 + 179 src/app/components/document-detail/document-detail.component.ts - 948 + 958 src/app/components/document-detail/document-detail.component.ts - 1255 + 1318 src/app/components/document-detail/document-detail.component.ts - 1294 + 1357 src/app/components/document-detail/document-detail.component.ts - 1335 + 1398 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 793 + 796 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 826 + 829 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 845 + 848 src/app/components/manage/custom-fields/custom-fields.component.ts @@ -2545,7 +2573,7 @@ src/app/components/manage/mail/mail.component.ts - 288 + 298 src/app/components/manage/management-list/management-list.component.ts @@ -2556,60 +2584,60 @@ 139 - - Deleted user + + Deleted user "" src/app/components/admin/users-groups/users-groups.component.ts 132 - - Error deleting user. + + Error deleting user "". src/app/components/admin/users-groups/users-groups.component.ts - 138 + 139 Saved group "". src/app/components/admin/users-groups/users-groups.component.ts - 156 + 159 Error saving group. src/app/components/admin/users-groups/users-groups.component.ts - 164 + 167 Confirm delete user group src/app/components/admin/users-groups/users-groups.component.ts - 172 + 175 This operation will permanently delete this user group. src/app/components/admin/users-groups/users-groups.component.ts - 173 + 176 - - Deleted group + + Deleted group "" src/app/components/admin/users-groups/users-groups.component.ts - 182 + 185 - - Error deleting group. + + Error deleting group "". src/app/components/admin/users-groups/users-groups.component.ts - 188 + 192 @@ -2623,83 +2651,83 @@ Logged in as src/app/components/app-frame/app-frame.component.html - 42 + 43 My Profile src/app/components/app-frame/app-frame.component.html - 46 + 47 Logout src/app/components/app-frame/app-frame.component.html - 53 + 54 Documentation src/app/components/app-frame/app-frame.component.html - 58 + 59 src/app/components/app-frame/app-frame.component.html - 288 + 289 src/app/components/app-frame/app-frame.component.html - 291 + 292 Saved views src/app/components/app-frame/app-frame.component.html - 98 + 99 src/app/components/app-frame/app-frame.component.html - 103 + 104 Open documents src/app/components/app-frame/app-frame.component.html - 130 + 131 Close all src/app/components/app-frame/app-frame.component.html - 150 + 151 src/app/components/app-frame/app-frame.component.html - 152 + 153 Manage src/app/components/app-frame/app-frame.component.html - 161 + 162 Correspondents src/app/components/app-frame/app-frame.component.html - 167 + 168 src/app/components/app-frame/app-frame.component.html - 169 + 170 src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -2710,11 +2738,11 @@ Tags src/app/components/app-frame/app-frame.component.html - 174 + 175 src/app/components/app-frame/app-frame.component.html - 177 + 178 src/app/components/common/input/tags/tags.component.ts @@ -2745,11 +2773,11 @@ Document Types src/app/components/app-frame/app-frame.component.html - 183 + 184 src/app/components/app-frame/app-frame.component.html - 185 + 186 src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -2760,11 +2788,11 @@ Storage Paths src/app/components/app-frame/app-frame.component.html - 190 + 191 src/app/components/app-frame/app-frame.component.html - 192 + 193 src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -2775,11 +2803,11 @@ Custom Fields src/app/components/app-frame/app-frame.component.html - 197 + 198 src/app/components/app-frame/app-frame.component.html - 199 + 200 src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.html @@ -2794,11 +2822,11 @@ Workflows src/app/components/app-frame/app-frame.component.html - 213 + 214 src/app/components/app-frame/app-frame.component.html - 215 + 216 src/app/components/manage/workflows/workflows.component.html @@ -2809,92 +2837,92 @@ Mail src/app/components/app-frame/app-frame.component.html - 220 + 221 src/app/components/app-frame/app-frame.component.html - 223 + 224 Administration src/app/components/app-frame/app-frame.component.html - 238 + 239 Configuration src/app/components/app-frame/app-frame.component.html - 251 + 252 src/app/components/app-frame/app-frame.component.html - 253 + 254 GitHub src/app/components/app-frame/app-frame.component.html - 298 + 299 is available. src/app/components/app-frame/app-frame.component.html - 307,308 + 308,309 Click to view. src/app/components/app-frame/app-frame.component.html - 308 + 309 Paperless-ngx can automatically check for updates src/app/components/app-frame/app-frame.component.html - 312 + 313 How does this work? src/app/components/app-frame/app-frame.component.html - 319,321 + 320,322 Update available src/app/components/app-frame/app-frame.component.html - 332 + 333 Sidebar views updated src/app/components/app-frame/app-frame.component.ts - 243 + 245 Error updating sidebar views src/app/components/app-frame/app-frame.component.ts - 246 + 248 An error occurred while saving update checking settings. src/app/components/app-frame/app-frame.component.ts - 267 + 269 @@ -2954,7 +2982,7 @@ src/app/components/document-detail/document-detail.component.html - 29 + 34 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -3082,6 +3110,20 @@ 250 + + Clear All + + src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html + 16 + + + + No notifications + + src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html + 20 + + Clear @@ -3115,31 +3157,31 @@ src/app/components/document-detail/document-detail.component.ts - 901 + 911 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 436 + 439 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 476 + 479 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 514 + 517 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 552 + 555 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 614 + 617 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 747 + 750 @@ -3294,48 +3336,114 @@ 93 - - True + + Today + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 39 + + + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 50 + + + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 76 + + + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 126 + + + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 152 + + + src/app/components/common/input/date/date.component.html + 21 + + + + Close src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html 40 - src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 51 + + + src/app/components/common/dates-dropdown/dates-dropdown.component.html 77 + + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 127 + + + src/app/components/common/dates-dropdown/dates-dropdown.component.html + 153 + + + src/app/components/common/input/date/date.component.html + 22 + + + src/app/components/document-detail/document-detail.component.html + 94 + + + src/app/components/document-detail/document-detail.component.ts + 1375 + + + src/app/guards/dirty-saved-view.guard.ts + 37 + + + + True src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 83 + 47 + + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 84 + + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 90 False src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 41 + 48 src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 78 + 85 src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 84 + 91 Search docs... src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 100 + 107 Any src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 132 + 139 src/app/components/common/filterable-dropdown/filterable-dropdown.component.html @@ -3346,7 +3454,7 @@ All src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 134 + 141 src/app/components/common/filterable-dropdown/filterable-dropdown.component.html @@ -3373,21 +3481,21 @@ Not src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 137 + 144 Add query src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 156 + 163 Add expression src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html - 159 + 166 @@ -3398,36 +3506,36 @@ src/app/components/common/dates-dropdown/dates-dropdown.component.html - 89 + 101 - - After + + From src/app/components/common/dates-dropdown/dates-dropdown.component.html 42 src/app/components/common/dates-dropdown/dates-dropdown.component.html - 106 + 118 - - Before + + To src/app/components/common/dates-dropdown/dates-dropdown.component.html - 62 + 68 src/app/components/common/dates-dropdown/dates-dropdown.component.html - 126 + 144 Added src/app/components/common/dates-dropdown/dates-dropdown.component.html - 74 + 86 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts @@ -3446,41 +3554,33 @@ 93 - - Last 7 days + + Within 1 week src/app/components/common/dates-dropdown/dates-dropdown.component.ts 67 - - Last month + + Within 1 month src/app/components/common/dates-dropdown/dates-dropdown.component.ts 72 - - src/app/pipes/custom-date.pipe.ts - 19 - - - Last 3 months + + Within 3 months src/app/components/common/dates-dropdown/dates-dropdown.component.ts 77 - - Last year + + Within 1 year src/app/components/common/dates-dropdown/dates-dropdown.component.ts 82 - - src/app/pipes/custom-date.pipe.ts - 14 - Matching algorithm @@ -3996,8 +4096,8 @@ 111 - src/app/components/common/toasts/toasts.component.html - 28 + src/app/components/common/toast/toast.component.html + 30 @@ -4184,7 +4284,7 @@ src/app/components/document-detail/document-detail.component.html - 283 + 288 @@ -5089,14 +5189,14 @@ Invalid date. src/app/components/common/input/date/date.component.html - 25 + 31 Suggestions: src/app/components/common/input/date/date.component.html - 31 + 37 src/app/components/common/input/select/select.component.html @@ -5111,7 +5211,7 @@ Filter documents with this src/app/components/common/input/date/date.component.ts - 121 + 123 src/app/components/common/input/select/select.component.ts @@ -5823,8 +5923,8 @@ 47 - src/app/components/common/toasts/toasts.component.html - 26 + src/app/components/common/toast/toast.component.html + 28 src/app/components/manage/mail/mail.component.html @@ -5915,8 +6015,8 @@ Copy Raw Error - src/app/components/common/toasts/toasts.component.html - 41 + src/app/components/common/toast/toast.component.html + 43 @@ -6161,7 +6261,7 @@ src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 381 + 384 this string is used to separate processing, failed and added on the file upload widget @@ -6225,14 +6325,14 @@ Download original src/app/components/document-detail/document-detail.component.html - 36 + 41 Reprocess src/app/components/document-detail/document-detail.component.html - 49 + 54 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -6243,7 +6343,7 @@ More like this src/app/components/document-detail/document-detail.component.html - 53 + 58 src/app/components/document-list/document-card-large/document-card-large.component.html @@ -6254,14 +6354,14 @@ Split src/app/components/document-detail/document-detail.component.html - 57 + 62 Rotate src/app/components/document-detail/document-detail.component.html - 61 + 66 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -6272,43 +6372,28 @@ Delete page(s) src/app/components/document-detail/document-detail.component.html - 65 - - - - Close - - src/app/components/document-detail/document-detail.component.html - 89 - - - src/app/components/document-detail/document-detail.component.ts - 1312 - - - src/app/guards/dirty-saved-view.guard.ts - 37 + 70 Previous src/app/components/document-detail/document-detail.component.html - 92 + 97 Details src/app/components/document-detail/document-detail.component.html - 105 + 110 Title src/app/components/document-detail/document-detail.component.html - 108 + 113 src/app/components/document-list/document-list.component.html @@ -6316,7 +6401,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 154 + 158 src/app/data/document.ts @@ -6331,21 +6416,21 @@ Archive serial number src/app/components/document-detail/document-detail.component.html - 109 + 114 Date created src/app/components/document-detail/document-detail.component.html - 110 + 115 Correspondent src/app/components/document-detail/document-detail.component.html - 112 + 117 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -6372,7 +6457,7 @@ Document type src/app/components/document-detail/document-detail.component.html - 114 + 119 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -6399,7 +6484,7 @@ Storage path src/app/components/document-detail/document-detail.component.html - 116 + 121 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -6422,7 +6507,7 @@ Default src/app/components/document-detail/document-detail.component.html - 117 + 122 src/app/components/manage/saved-views/saved-views.component.html @@ -6433,14 +6518,14 @@ Content src/app/components/document-detail/document-detail.component.html - 213 + 218 Metadata src/app/components/document-detail/document-detail.component.html - 222 + 227 src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts @@ -6451,175 +6536,175 @@ Date modified src/app/components/document-detail/document-detail.component.html - 229 + 234 Date added src/app/components/document-detail/document-detail.component.html - 233 + 238 Media filename src/app/components/document-detail/document-detail.component.html - 237 + 242 Original filename src/app/components/document-detail/document-detail.component.html - 241 + 246 Original MD5 checksum src/app/components/document-detail/document-detail.component.html - 245 + 250 Original file size src/app/components/document-detail/document-detail.component.html - 249 + 254 Original mime type src/app/components/document-detail/document-detail.component.html - 253 + 258 Archive MD5 checksum src/app/components/document-detail/document-detail.component.html - 258 + 263 Archive file size src/app/components/document-detail/document-detail.component.html - 264 + 269 Original document metadata src/app/components/document-detail/document-detail.component.html - 273 + 278 Archived document metadata src/app/components/document-detail/document-detail.component.html - 276 + 281 Notes src/app/components/document-detail/document-detail.component.html - 295,298 + 300,303 History src/app/components/document-detail/document-detail.component.html - 306 + 311 Save & next src/app/components/document-detail/document-detail.component.html - 343 + 348 Save & close src/app/components/document-detail/document-detail.component.html - 346 + 351 Document loading... src/app/components/document-detail/document-detail.component.html - 356 + 361 Enter Password src/app/components/document-detail/document-detail.component.html - 410 + 415 An error occurred loading content: src/app/components/document-detail/document-detail.component.ts - 406,408 + 412,414 Document changes detected src/app/components/document-detail/document-detail.component.ts - 436 + 435 The version of this document in your browser session appears older than the existing version. src/app/components/document-detail/document-detail.component.ts - 437 + 436 Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document. src/app/components/document-detail/document-detail.component.ts - 438 + 437 Ok src/app/components/document-detail/document-detail.component.ts - 440 + 439 Next document src/app/components/document-detail/document-detail.component.ts - 547 + 546 Previous document src/app/components/document-detail/document-detail.component.ts - 557 + 556 Close document src/app/components/document-detail/document-detail.component.ts - 565 + 564 src/app/services/open-documents.service.ts @@ -6630,224 +6715,241 @@ Save document src/app/components/document-detail/document-detail.component.ts - 572 + 571 Save and close / next src/app/components/document-detail/document-detail.component.ts - 581 + 580 Error retrieving metadata src/app/components/document-detail/document-detail.component.ts - 633 + 632 Error retrieving suggestions. src/app/components/document-detail/document-detail.component.ts - 662 + 661 - - Document saved successfully. + + Document "" saved successfully. src/app/components/document-detail/document-detail.component.ts 813 src/app/components/document-detail/document-detail.component.ts - 827 + 829 + + + + Error saving document "" + + src/app/components/document-detail/document-detail.component.ts + 835 Error saving document src/app/components/document-detail/document-detail.component.ts - 831 - - - src/app/components/document-detail/document-detail.component.ts - 874 + 880 Do you really want to move the document "" to the trash? src/app/components/document-detail/document-detail.component.ts - 902 + 912 Documents can be restored prior to permanent deletion. src/app/components/document-detail/document-detail.component.ts - 903 + 913 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 749 + 752 Move to trash src/app/components/document-detail/document-detail.component.ts - 905 + 915 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 751 + 754 + + + + Error deleting document + + src/app/components/document-detail/document-detail.component.ts + 934 Reprocess confirm src/app/components/document-detail/document-detail.component.ts - 944 + 954 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 789 + 792 This operation will permanently recreate the archive file for this document. src/app/components/document-detail/document-detail.component.ts - 945 + 955 The archive file will be re-generated with the current settings. src/app/components/document-detail/document-detail.component.ts - 946 + 956 - - Reprocess operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. + + Reprocess operation for "" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. src/app/components/document-detail/document-detail.component.ts - 956 + 966 Error executing operation src/app/components/document-detail/document-detail.component.ts - 967 + 977 + + + + Error downloading document + + src/app/components/document-detail/document-detail.component.ts + 1024 Page Fit src/app/components/document-detail/document-detail.component.ts - 1040 + 1103 Split confirm src/app/components/document-detail/document-detail.component.ts - 1253 + 1316 This operation will split the selected document(s) into new documents. src/app/components/document-detail/document-detail.component.ts - 1254 + 1317 - - Split operation will begin in the background. + + Split operation for "" will begin in the background. src/app/components/document-detail/document-detail.component.ts - 1270 + 1333 Error executing split operation src/app/components/document-detail/document-detail.component.ts - 1279 + 1342 Rotate confirm src/app/components/document-detail/document-detail.component.ts - 1292 + 1355 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 823 + 826 This operation will permanently rotate the original version of the current document. src/app/components/document-detail/document-detail.component.ts - 1293 + 1356 - - Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes. + + Rotation of "" will begin in the background. Close and re-open the document after the operation has completed to see the changes. src/app/components/document-detail/document-detail.component.ts - 1309 + 1372 Error executing rotate operation src/app/components/document-detail/document-detail.component.ts - 1321 + 1384 Delete pages confirm src/app/components/document-detail/document-detail.component.ts - 1333 + 1396 This operation will permanently delete the selected pages from the original document. src/app/components/document-detail/document-detail.component.ts - 1334 + 1397 - - Delete pages operation will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes. + + Delete pages operation for "" will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes. src/app/components/document-detail/document-detail.component.ts - 1349 + 1412 Error executing delete pages operation src/app/components/document-detail/document-detail.component.ts - 1358 + 1421 An error occurred loading tiff: src/app/components/document-detail/document-detail.component.ts - 1398 + 1461 src/app/components/document-detail/document-detail.component.ts - 1402 + 1465 @@ -6927,7 +7029,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 162 + 166 @@ -6983,25 +7085,25 @@ Error executing bulk operation src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 285 + 288 "" src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 373 + 376 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 379 + 382 "" and "" src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 375 + 378 This is for messages like 'modify "tag1" and "tag2"' @@ -7009,7 +7111,7 @@ and "" src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 383,385 + 386,388 this is for messages like 'modify "tag1", "tag2" and "tag3"' @@ -7017,14 +7119,14 @@ Confirm tags assignment src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 400 + 403 This operation will add the tag "" to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 406 + 409 @@ -7033,14 +7135,14 @@ )"/> to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 411,413 + 414,416 This operation will remove the tag "" from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 419 + 422 @@ -7049,7 +7151,7 @@ )"/> from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 424,426 + 427,429 @@ -7060,84 +7162,84 @@ )"/> on selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 428,432 + 431,435 Confirm correspondent assignment src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 469 + 472 This operation will assign the correspondent "" to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 471 + 474 This operation will remove the correspondent from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 473 + 476 Confirm document type assignment src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 507 + 510 This operation will assign the document type "" to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 509 + 512 This operation will remove the document type from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 511 + 514 Confirm storage path assignment src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 545 + 548 This operation will assign the storage path "" to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 547 + 550 This operation will remove the storage path from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 549 + 552 Confirm custom field assignment src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 578 + 581 This operation will assign the custom field "" to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 584 + 587 @@ -7146,14 +7248,14 @@ )"/> to selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 589,591 + 592,594 This operation will remove the custom field "" from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 597 + 600 @@ -7162,7 +7264,7 @@ )"/> from selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 602,604 + 605,607 @@ -7173,70 +7275,70 @@ )"/> on selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 606,610 + 609,613 Move selected document(s) to the trash? src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 748 + 751 This operation will permanently recreate the archive files for selected document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 790 + 793 The archive files will be re-generated with the current settings. src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 791 + 794 This operation will permanently rotate the original version of document(s). src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 824 + 827 Merge confirm src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 843 + 846 This operation will merge selected documents into a new document. src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 844 + 847 Merged document will be queued for consumption. src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 860 + 863 Custom fields updated. src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 882 + 885 Error updating custom fields. src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 891 + 894 @@ -7415,7 +7517,7 @@ src/app/components/document-list/document-list.component.ts - 310 + 314 @@ -7426,7 +7528,7 @@ src/app/components/document-list/document-list.component.ts - 303 + 307 @@ -7529,7 +7631,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 159 + 163 src/app/data/document.ts @@ -7669,42 +7771,42 @@ Reset filters / selection src/app/components/document-list/document-list.component.ts - 291 + 295 Open first [selected] document src/app/components/document-list/document-list.component.ts - 319 + 323 Previous page src/app/components/document-list/document-list.component.ts - 335 + 339 Next page src/app/components/document-list/document-list.component.ts - 347 + 351 View "" saved successfully. src/app/components/document-list/document-list.component.ts - 379 + 383 View "" created successfully. src/app/components/document-list/document-list.component.ts - 422 + 426 @@ -7718,147 +7820,147 @@ Title & content src/app/components/document-list/filter-editor/filter-editor.component.ts - 157 + 161 More like src/app/components/document-list/filter-editor/filter-editor.component.ts - 172 + 176 equals src/app/components/document-list/filter-editor/filter-editor.component.ts - 178 + 182 is empty src/app/components/document-list/filter-editor/filter-editor.component.ts - 182 + 186 is not empty src/app/components/document-list/filter-editor/filter-editor.component.ts - 186 + 190 greater than src/app/components/document-list/filter-editor/filter-editor.component.ts - 190 + 194 less than src/app/components/document-list/filter-editor/filter-editor.component.ts - 194 + 198 Correspondent: src/app/components/document-list/filter-editor/filter-editor.component.ts - 226,228 + 230,232 Without correspondent src/app/components/document-list/filter-editor/filter-editor.component.ts - 230 + 234 Document type: src/app/components/document-list/filter-editor/filter-editor.component.ts - 236,238 + 240,242 Without document type src/app/components/document-list/filter-editor/filter-editor.component.ts - 240 + 244 Storage path: src/app/components/document-list/filter-editor/filter-editor.component.ts - 246,248 + 250,252 Without storage path src/app/components/document-list/filter-editor/filter-editor.component.ts - 250 + 254 Tag: src/app/components/document-list/filter-editor/filter-editor.component.ts - 254,256 + 258,260 Without any tag src/app/components/document-list/filter-editor/filter-editor.component.ts - 260 + 264 Custom fields query src/app/components/document-list/filter-editor/filter-editor.component.ts - 264 + 268 Title: src/app/components/document-list/filter-editor/filter-editor.component.ts - 267 + 271 ASN: src/app/components/document-list/filter-editor/filter-editor.component.ts - 270 + 274 Owner: src/app/components/document-list/filter-editor/filter-editor.component.ts - 273 + 277 Owner not in: src/app/components/document-list/filter-editor/filter-editor.component.ts - 276 + 280 Without an owner src/app/components/document-list/filter-editor/filter-editor.component.ts - 279 + 283 @@ -8021,19 +8123,19 @@ src/app/components/manage/management-list/management-list.component.html - 85 + 86 src/app/components/manage/management-list/management-list.component.html - 85 + 86 src/app/components/manage/management-list/management-list.component.html - 85 + 86 src/app/components/manage/management-list/management-list.component.html - 85 + 86 @@ -8057,18 +8159,18 @@ 102 - - Deleted field + + Deleted field "" src/app/components/manage/custom-fields/custom-fields.component.ts 111 - - Error deleting field. + + Error deleting field "". src/app/components/manage/custom-fields/custom-fields.component.ts - 117 + 118 @@ -8247,109 +8349,109 @@ 194 - - Deleted mail account + + Deleted mail account "" src/app/components/manage/mail/mail.component.ts - 203 + 204 - - Error deleting mail account. + + Error deleting mail account "". src/app/components/manage/mail/mail.component.ts - 213 + 215 - - Processing mail account - - src/app/components/manage/mail/mail.component.ts - 224 - - - - Error processing mail account + + Processing mail account "" src/app/components/manage/mail/mail.component.ts 227 + + Error processing mail account "" + + src/app/components/manage/mail/mail.component.ts + 232 + + Saved rule "". src/app/components/manage/mail/mail.component.ts - 243 + 250 Error saving rule. src/app/components/manage/mail/mail.component.ts - 254 + 261 Rule "" enabled. src/app/components/manage/mail/mail.component.ts - 270 + 277 Rule "" disabled. src/app/components/manage/mail/mail.component.ts - 271 + 278 - - Error toggling rule. + + Error toggling rule "". src/app/components/manage/mail/mail.component.ts - 275 + 283 Confirm delete mail rule src/app/components/manage/mail/mail.component.ts - 284 + 294 This operation will permanently delete this mail rule. src/app/components/manage/mail/mail.component.ts - 285 + 295 - - Deleted mail rule + + Deleted mail rule "" src/app/components/manage/mail/mail.component.ts - 294 + 305 - - Error deleting mail rule. + + Error deleting mail rule "". src/app/components/manage/mail/mail.component.ts - 303 + 316 Permissions updated src/app/components/manage/mail/mail.component.ts - 325 + 340 Error updating permissions src/app/components/manage/mail/mail.component.ts - 330 + 345 src/app/components/manage/management-list/management-list.component.ts @@ -8417,19 +8519,19 @@ {VAR_PLURAL, plural, =1 {One } other { total }} src/app/components/manage/management-list/management-list.component.html - 116 + 118 src/app/components/manage/management-list/management-list.component.html - 116 + 118 src/app/components/manage/management-list/management-list.component.html - 116 + 118 src/app/components/manage/management-list/management-list.component.html - 116 + 118 @@ -8468,8 +8570,8 @@ 183 - - Successfully updated . + + Successfully updated "". src/app/components/manage/management-list/management-list.component.ts 198 @@ -8685,39 +8787,39 @@ 136 - - Deleted workflow + + Deleted workflow "". src/app/components/manage/workflows/workflows.component.ts - 145 + 146 - - Error deleting workflow. + + Error deleting workflow "". src/app/components/manage/workflows/workflows.component.ts - 150 + 153 - - Enabled workflow + + Enabled workflow "" src/app/components/manage/workflows/workflows.component.ts - 161 + 166 - - Disabled workflow + + Disabled workflow "" src/app/components/manage/workflows/workflows.component.ts - 162 + 167 - - Error toggling workflow. + + Error toggling workflow "". src/app/components/manage/workflows/workflows.component.ts - 168 + 174 @@ -9150,6 +9252,13 @@ 36 + + Last year + + src/app/pipes/custom-date.pipe.ts + 14 + + %s years ago @@ -9157,6 +9266,13 @@ 15 + + Last month + + src/app/pipes/custom-date.pipe.ts + 19 + + %s months ago @@ -9234,122 +9350,6 @@ 11 - - Document already exists. - - src/app/services/consumer-status.service.ts - 17 - - - - Document already exists. Note: existing document is in the trash. - - src/app/services/consumer-status.service.ts - 18 - - - - Document with ASN already exists. - - src/app/services/consumer-status.service.ts - 19 - - - - Document with ASN already exists. Note: existing document is in the trash. - - src/app/services/consumer-status.service.ts - 20 - - - - File not found. - - src/app/services/consumer-status.service.ts - 21 - - - - Pre-consume script does not exist. - - src/app/services/consumer-status.service.ts - 22 - - Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation - - - Error while executing pre-consume script. - - src/app/services/consumer-status.service.ts - 23 - - Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation - - - Post-consume script does not exist. - - src/app/services/consumer-status.service.ts - 24 - - Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation - - - Error while executing post-consume script. - - src/app/services/consumer-status.service.ts - 25 - - Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation - - - Received new file. - - src/app/services/consumer-status.service.ts - 26 - - - - File type not supported. - - src/app/services/consumer-status.service.ts - 27 - - - - Processing document... - - src/app/services/consumer-status.service.ts - 28 - - - - Generating thumbnail... - - src/app/services/consumer-status.service.ts - 29 - - - - Retrieving date from document... - - src/app/services/consumer-status.service.ts - 30 - - - - Saving document... - - src/app/services/consumer-status.service.ts - 31 - - - - Finished. - - src/app/services/consumer-status.service.ts - 32 - - You have unsaved changes to the document @@ -9665,6 +9665,122 @@ 70 + + Document already exists. + + src/app/services/websocket-status.service.ts + 23 + + + + Document already exists. Note: existing document is in the trash. + + src/app/services/websocket-status.service.ts + 24 + + + + Document with ASN already exists. + + src/app/services/websocket-status.service.ts + 25 + + + + Document with ASN already exists. Note: existing document is in the trash. + + src/app/services/websocket-status.service.ts + 26 + + + + File not found. + + src/app/services/websocket-status.service.ts + 27 + + + + Pre-consume script does not exist. + + src/app/services/websocket-status.service.ts + 28 + + Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation + + + Error while executing pre-consume script. + + src/app/services/websocket-status.service.ts + 29 + + Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation + + + Post-consume script does not exist. + + src/app/services/websocket-status.service.ts + 30 + + Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation + + + Error while executing post-consume script. + + src/app/services/websocket-status.service.ts + 31 + + Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation + + + Received new file. + + src/app/services/websocket-status.service.ts + 32 + + + + File type not supported. + + src/app/services/websocket-status.service.ts + 33 + + + + Processing document... + + src/app/services/websocket-status.service.ts + 34 + + + + Generating thumbnail... + + src/app/services/websocket-status.service.ts + 35 + + + + Retrieving date from document... + + src/app/services/websocket-status.service.ts + 36 + + + + Saving document... + + src/app/services/websocket-status.service.ts + 37 + + + + Finished. + + src/app/services/websocket-status.service.ts + 38 + + diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index c09875247..37314ebb1 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -9,17 +9,17 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@angular/cdk": "^19.0.2", - "@angular/common": "~19.0.3", - "@angular/compiler": "~19.0.3", - "@angular/core": "~19.0.3", - "@angular/forms": "~19.0.3", - "@angular/localize": "~19.0.3", - "@angular/platform-browser": "~19.0.3", - "@angular/platform-browser-dynamic": "~19.0.3", - "@angular/router": "~19.0.3", + "@angular/cdk": "^19.1.2", + "@angular/common": "~19.1.4", + "@angular/compiler": "~19.1.4", + "@angular/core": "~19.1.4", + "@angular/forms": "~19.1.4", + "@angular/localize": "~19.1.4", + "@angular/platform-browser": "~19.1.4", + "@angular/platform-browser-dynamic": "~19.1.4", + "@angular/router": "~19.1.4", "@ng-bootstrap/ng-bootstrap": "^18.0.0", - "@ng-select/ng-select": "^14.1.0", + "@ng-select/ng-select": "^14.2.0", "@ngneat/dirty-check-forms": "^3.0.3", "@popperjs/core": "^2.11.8", "bootstrap": "^5.3.3", @@ -28,36 +28,37 @@ "ng2-pdf-viewer": "^10.4.0", "ngx-bootstrap-icons": "^1.9.3", "ngx-color": "^9.0.0", - "ngx-cookie-service": "^19.0.0", + "ngx-cookie-service": "^19.1.0", + "ngx-device-detector": "^9.0.0", "ngx-file-drop": "^16.0.0", "ngx-ui-tour-ng-bootstrap": "^16.0.0", "rxjs": "^7.8.1", "tslib": "^2.8.1", "utif": "^3.1.0", - "uuid": "^11.0.2", + "uuid": "^11.0.5", "zone.js": "^0.15.0" }, "devDependencies": { - "@angular-builders/custom-webpack": "^19.0.0-beta.0", - "@angular-builders/jest": "^19.0.0-beta.1", + "@angular-builders/custom-webpack": "^19.0.0", + "@angular-builders/jest": "^19.0.0", "@angular-devkit/build-angular": "^19.0.4", - "@angular-devkit/core": "^19.0.4", - "@angular-devkit/schematics": "^19.0.4", - "@angular-eslint/builder": "19.0.0", - "@angular-eslint/eslint-plugin": "19.0.0", - "@angular-eslint/eslint-plugin-template": "19.0.0", - "@angular-eslint/schematics": "19.0.0", - "@angular-eslint/template-parser": "19.0.0", - "@angular/cli": "~19.0.4", - "@angular/compiler-cli": "~19.0.3", - "@codecov/webpack-plugin": "^1.2.1", - "@playwright/test": "^1.48.2", + "@angular-devkit/core": "^19.1.5", + "@angular-devkit/schematics": "^19.1.5", + "@angular-eslint/builder": "19.0.2", + "@angular-eslint/eslint-plugin": "19.0.2", + "@angular-eslint/eslint-plugin-template": "19.0.2", + "@angular-eslint/schematics": "19.0.2", + "@angular-eslint/template-parser": "19.0.2", + "@angular/cli": "~19.1.5", + "@angular/compiler-cli": "~19.1.4", + "@codecov/webpack-plugin": "^1.8.0", + "@playwright/test": "^1.50.1", "@types/jest": "^29.5.14", - "@types/node": "^22.8.6", - "@typescript-eslint/eslint-plugin": "^8.12.2", - "@typescript-eslint/parser": "^8.12.2", + "@types/node": "^22.13.0", + "@typescript-eslint/eslint-plugin": "^8.22.0", + "@typescript-eslint/parser": "^8.22.0", "@typescript-eslint/utils": "^8.0.0", - "eslint": "^9.14.0", + "eslint": "^9.19.0", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-preset-angular": "^14.4.2", @@ -82,6 +83,7 @@ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", "dev": true, + "license": "MIT", "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" @@ -92,6 +94,7 @@ "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", "dev": true, + "license": "MIT", "dependencies": { "@actions/io": "^1.0.1" } @@ -101,6 +104,7 @@ "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", "dev": true, + "license": "MIT", "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", @@ -113,6 +117,7 @@ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", "dev": true, + "license": "MIT", "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" @@ -122,7 +127,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@ampproject/remapping": { "version": "2.3.0", @@ -137,9 +143,9 @@ } }, "node_modules/@angular-builders/common": { - "version": "3.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-3.0.0-beta.0.tgz", - "integrity": "sha512-3OUBr4UMUoyZJkDxne3HWAKLMLYZWf3nTQ/34ICErvkfBnV9NPsYs47nGuKechHWqhz7MVk2JeKztZw3hXWobA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-3.0.0.tgz", + "integrity": "sha512-AACGMwlBFYF3PaFekgJDCmqO1hMBrK5eyjHMN5aqJk3PV46BhnlNcQEa9pftLUKxoGijXBQzlalDZkceatyoMw==", "dev": true, "license": "MIT", "dependencies": { @@ -152,13 +158,13 @@ } }, "node_modules/@angular-builders/custom-webpack": { - "version": "19.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-19.0.0-beta.0.tgz", - "integrity": "sha512-FdpYku8Q9rh6L04FU+yMZqWGKVY2OD9k5I0veIztYLoRFc9VxEPp7gmoO/9GmXfCL90zv89BPJw/QZ7sonQ5YA==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-19.0.0.tgz", + "integrity": "sha512-MD3N+OPl/50u+N6YJ6UwS1kOT5C6RkOsSih8F88lR/TACQCSDf0FLmJmROktNgRNADhySGnK18o874Vftyqi4w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-builders/common": "3.0.0-beta.0", + "@angular-builders/common": "3.0.0", "@angular-devkit/architect": ">=0.1900.0 < 0.2000.0", "@angular-devkit/build-angular": "^19.0.0", "@angular-devkit/core": "^19.0.0", @@ -173,13 +179,13 @@ } }, "node_modules/@angular-builders/jest": { - "version": "19.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-19.0.0-beta.1.tgz", - "integrity": "sha512-s6NeJGHf09F/kArhnxj+DypEQ1nuloA5RwmkTjCCCGAsXnS8WdJiyizaTmWj1WiSnYkY2431HqjLfwvUmRo3qQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-19.0.0.tgz", + "integrity": "sha512-DEKragHT26kwUhXx9goYehQ/WxFzpVrMHIicYF+L7sLVmFyCwPwAslYZZBe/eTm/x++tKRHnun1lbcf1ZDccRg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-builders/common": "3.0.0-beta.0", + "@angular-builders/common": "3.0.0", "@angular-devkit/architect": ">=0.1900.0 < 0.2000.0", "@angular-devkit/core": "^19.0.0", "jest-preset-angular": "14.4.2", @@ -196,14 +202,42 @@ "jest": ">=29" } }, - "node_modules/@angular-devkit/architect": { - "version": "0.1900.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1900.4.tgz", - "integrity": "sha512-9XwZ21BPYS2vGOOwVB40fsMyuwJT0H1lWaAMo8Umwi6XbKBVfaWbEhjtR9dlarrySKtFuTz9hmTZkIXHLjXPdA==", + "node_modules/@angular-builders/jest/node_modules/jest-preset-angular": { + "version": "14.4.2", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.4.2.tgz", + "integrity": "sha512-BYYv0FaTDfBNh8WyA9mpOV3krfw20kurBGK8INZUnv7KZDAWZuQtCET4TwTWxSNQ9jS1OX1+a5weCm/bTDDM1A==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.0.4", + "bs-logger": "^0.2.6", + "esbuild-wasm": ">=0.15.13", + "jest-environment-jsdom": "^29.0.0", + "jest-util": "^29.0.0", + "pretty-format": "^29.0.0", + "ts-jest": "^29.0.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "optionalDependencies": { + "esbuild": ">=0.15.13" + }, + "peerDependencies": { + "@angular/compiler-cli": ">=15.0.0 <20.0.0", + "@angular/core": ">=15.0.0 <20.0.0", + "@angular/platform-browser-dynamic": ">=15.0.0 <20.0.0", + "jest": "^29.0.0", + "typescript": ">=4.8" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1901.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1901.5.tgz", + "integrity": "sha512-zlRudZx34FkFZnSdaQCjxDleHwbQYNLdBFcLi+FBwt0UXqxmhbEIasK3l/3kCOC3QledrjUzVXgouji+OZ/WGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.1.5", "rxjs": "7.8.1" }, "engines": { @@ -213,19 +247,19 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.0.4.tgz", - "integrity": "sha512-n7fcRdNB7ed5j6aZI+qPI/1LylFv1OiRNgBIeJxX3HEmzQxsHHLcxWog2yZK2Fvw3390xFx/VjZaklITj6tBFA==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.1.5.tgz", + "integrity": "sha512-ny7ktNOTxaEi6cS3V6XFP5bbJkgiMt3OUNUYLdfdbv4y6wolVlPVHKl+wb4xs6tgbnmx63+e6zGpoDMCRytgcg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1900.4", - "@angular-devkit/build-webpack": "0.1900.4", - "@angular-devkit/core": "19.0.4", - "@angular/build": "19.0.4", + "@angular-devkit/architect": "0.1901.5", + "@angular-devkit/build-webpack": "0.1901.5", + "@angular-devkit/core": "19.1.5", + "@angular/build": "19.1.5", "@babel/core": "7.26.0", - "@babel/generator": "7.26.2", + "@babel/generator": "7.26.3", "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", "@babel/plugin-transform-async-generator-functions": "7.25.9", @@ -234,21 +268,21 @@ "@babel/preset-env": "7.26.0", "@babel/runtime": "7.26.0", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "19.0.4", - "@vitejs/plugin-basic-ssl": "1.1.0", + "@ngtools/webpack": "19.1.5", + "@vitejs/plugin-basic-ssl": "1.2.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "browserslist": "^4.21.5", "copy-webpack-plugin": "12.0.2", "css-loader": "7.1.2", - "esbuild-wasm": "0.24.0", - "fast-glob": "3.3.2", + "esbuild-wasm": "0.24.2", + "fast-glob": "3.3.3", "http-proxy-middleware": "3.0.3", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", - "less": "4.2.0", + "less": "4.2.1", "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.3.1", @@ -256,22 +290,22 @@ "open": "10.1.0", "ora": "5.4.1", "picomatch": "4.0.2", - "piscina": "4.7.0", + "piscina": "4.8.0", "postcss": "8.4.49", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.80.7", - "sass-loader": "16.0.3", + "sass": "1.83.1", + "sass-loader": "16.0.4", "semver": "7.6.3", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.36.0", + "terser": "5.37.0", "tree-kill": "1.2.2", "tslib": "2.8.1", - "webpack": "5.96.1", + "webpack": "5.97.1", "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.1.0", + "webpack-dev-server": "5.2.0", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, @@ -281,14 +315,14 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.24.0" + "esbuild": "0.24.2" }, "peerDependencies": { "@angular/compiler-cli": "^19.0.0", "@angular/localize": "^19.0.0", "@angular/platform-server": "^19.0.0", "@angular/service-worker": "^19.0.0", - "@angular/ssr": "^19.0.4", + "@angular/ssr": "^19.1.5", "@web/test-runner": "^0.19.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", @@ -296,8 +330,8 @@ "karma": "^6.3.0", "ng-packagr": "^19.0.0", "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.5 <5.7" + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.5 <5.8" }, "peerDependenciesMeta": { "@angular/localize": { @@ -338,27 +372,92 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", - "cpu": [ - "ppc64" - ], + "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.1.5.tgz", + "integrity": "sha512-byoHcv0/s6WGWap59s43N/eC+4NsviuTnGoj+iR0ayubk8snn6jdkZLbFDfnTuQlTiu4ok8/XcksjzeMkgGyyw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1901.5", + "@angular-devkit/core": "19.1.5", + "@babel/core": "7.26.0", + "@babel/helper-annotate-as-pure": "7.25.9", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.26.0", + "@inquirer/confirm": "5.1.1", + "@vitejs/plugin-basic-ssl": "1.2.0", + "beasties": "0.2.0", + "browserslist": "^4.23.0", + "esbuild": "0.24.2", + "fast-glob": "3.3.3", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "listr2": "8.2.5", + "magic-string": "0.30.17", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.8.0", + "rollup": "4.30.1", + "sass": "1.83.1", + "semver": "7.6.3", + "vite": "6.0.11", + "watchpack": "2.4.2" + }, "engines": { - "node": ">=18" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.2.2" + }, + "peerDependencies": { + "@angular/compiler": "^19.0.0", + "@angular/compiler-cli": "^19.0.0", + "@angular/localize": "^19.0.0", + "@angular/platform-server": "^19.0.0", + "@angular/service-worker": "^19.0.0", + "@angular/ssr": "^19.1.5", + "less": "^4.2.0", + "ng-packagr": "^19.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.5 <5.8" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", + "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==", "cpu": [ "arm" ], @@ -367,15 +466,12 @@ "optional": true, "os": [ "android" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz", + "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==", "cpu": [ "arm64" ], @@ -384,32 +480,12 @@ "optional": true, "os": [ "android" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz", + "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==", "cpu": [ "arm64" ], @@ -418,15 +494,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz", + "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==", "cpu": [ "x64" ], @@ -435,15 +508,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz", + "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==", "cpu": [ "arm64" ], @@ -452,15 +522,12 @@ "optional": true, "os": [ "freebsd" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz", + "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==", "cpu": [ "x64" ], @@ -469,15 +536,12 @@ "optional": true, "os": [ "freebsd" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz", + "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==", "cpu": [ "arm" ], @@ -486,15 +550,26 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz", + "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz", + "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==", "cpu": [ "arm64" ], @@ -503,32 +578,26 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz", + "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==", "cpu": [ - "ia32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz", + "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==", "cpu": [ "loong64" ], @@ -537,32 +606,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz", + "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==", "cpu": [ "ppc64" ], @@ -571,15 +620,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz", + "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==", "cpu": [ "riscv64" ], @@ -588,15 +634,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz", + "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==", "cpu": [ "s390x" ], @@ -605,15 +648,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz", + "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==", "cpu": [ "x64" ], @@ -622,15 +662,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz", + "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==", "cpu": [ "x64" ], @@ -638,50 +675,13 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz", + "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==", "cpu": [ "arm64" ], @@ -690,15 +690,12 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz", + "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==", "cpu": [ "ia32" ], @@ -707,15 +704,12 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz", + "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==", "cpu": [ "x64" ], @@ -724,57 +718,14 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" - } + ] }, "node_modules/@angular-devkit/build-angular/node_modules/istanbul-lib-instrument": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -786,14 +737,53 @@ "node": ">=10" } }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.1900.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1900.4.tgz", - "integrity": "sha512-eovr5Am8EwxF7d/y0Hbfz/KYWnOXXVXVwquPUcg8JBI19lLbfctz4+71Vjz2qGroijr2FlZztRpmhd498SLt/A==", + "node_modules/@angular-devkit/build-angular/node_modules/rollup": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz", + "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1900.4", + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.30.1", + "@rollup/rollup-android-arm64": "4.30.1", + "@rollup/rollup-darwin-arm64": "4.30.1", + "@rollup/rollup-darwin-x64": "4.30.1", + "@rollup/rollup-freebsd-arm64": "4.30.1", + "@rollup/rollup-freebsd-x64": "4.30.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.30.1", + "@rollup/rollup-linux-arm-musleabihf": "4.30.1", + "@rollup/rollup-linux-arm64-gnu": "4.30.1", + "@rollup/rollup-linux-arm64-musl": "4.30.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.30.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1", + "@rollup/rollup-linux-riscv64-gnu": "4.30.1", + "@rollup/rollup-linux-s390x-gnu": "4.30.1", + "@rollup/rollup-linux-x64-gnu": "4.30.1", + "@rollup/rollup-linux-x64-musl": "4.30.1", + "@rollup/rollup-win32-arm64-msvc": "4.30.1", + "@rollup/rollup-win32-ia32-msvc": "4.30.1", + "@rollup/rollup-win32-x64-msvc": "4.30.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1901.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1901.5.tgz", + "integrity": "sha512-UxEoF7F8L1GpH/N4me7VGe5ZPfxIiVHyhw5/ck3rcVbT6YD22/GYFGSJRGYP+D7LLTJ7OOQvfD6Bc/q62HhWvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.1901.5", "rxjs": "7.8.1" }, "engines": { @@ -807,9 +797,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.4.tgz", - "integrity": "sha512-+imxIj1JLr2hbUYQePHgkTUKr0VmlxNSZvIREcCWtXUcdCypiwhJAtGXv6MfpB4hAx+FJZYEpVWeLwYOS/gW0A==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.5.tgz", + "integrity": "sha512-wGKV+i5mCM/Hd/3CsdrIYcVi5G2Wg/D5941bUDXivrbsqHfKVINxAkI3OI1eaD90VnAL8ICrQEoAhh6ni2Umkg==", "dev": true, "license": "MIT", "dependencies": { @@ -852,15 +842,15 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.0.4.tgz", - "integrity": "sha512-2r6Qs4N5NSPho+qzegCYS8kIgylXyH4DHaS7HJ5+4XvM1I8V8AII8payLWkUK0i29XufVoD5XfPUFnjxZrBfYQ==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.1.5.tgz", + "integrity": "sha512-8QjOlO2CktcTT0TWcaABea2xSePxoPKaZu96+6gc8oZzj/y8DbdGiO9mRvIac9+m4hiZI41Cqm1W+yMsCzYMkA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.0.4", + "@angular-devkit/core": "19.1.5", "jsonc-parser": "3.3.1", - "magic-string": "0.30.12", + "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" }, @@ -871,9 +861,9 @@ } }, "node_modules/@angular-eslint/builder": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.0.0.tgz", - "integrity": "sha512-vi68ADoEKrg2SB87jwUCaVhOhWPpXyG6X8QJzg8AiYDCQY721x1l6Pdz6WZOPruWALyoIyFGFXqtuysDGqIBhw==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.0.2.tgz", + "integrity": "sha512-BdmMSndQt2fSBiTVniskUcUpQaeweUapbsL0IDfQ7a13vL0NVXpc3K89YXuVE/xsb08uHtqphuwxPAAj6kX3OA==", "dev": true, "license": "MIT", "dependencies": { @@ -886,21 +876,21 @@ } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.0.0.tgz", - "integrity": "sha512-q6IaiqKYcmBW/gw55tytDucguo5E48szVCLNLHUFdN98YDDsP+KM3MPWYPyZcXpusmFfIjLdr8d41PlKmyMUpg==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.0.2.tgz", + "integrity": "sha512-HPmp92r70SNO/0NdIaIhxrgVSpomqryuUk7jszvNRtu+OzYCJGcbLhQD38T3dbBWT/AV0QXzyzExn6/2ai9fEw==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.0.0.tgz", - "integrity": "sha512-WkUnH5zmvC/cH6f8BGiRK+KebrKdGbQmhtu3IHLEyzG9U4mBiIV8XkSzhdkY3RCN8bKqhmE5C3oNBLNCtvg4QQ==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.0.2.tgz", + "integrity": "sha512-DLuNVVGGFicSThOcMSJyNje+FZSPdG0B3lCBRiqcgKH/16kfM4pV8MobPM7RGK2NhaOmmZ4zzJNwpwWPSgi+Lw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0", - "@angular-eslint/utils": "19.0.0" + "@angular-eslint/bundled-angular-compiler": "19.0.2", + "@angular-eslint/utils": "19.0.2" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -909,14 +899,14 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.0.0.tgz", - "integrity": "sha512-d2NzuAyvFo00QGBv6BLno0KZ3Ptd+UNVHpI9vwU0giaZcjVsdKbcMvMfynkvHAAwVIVw5aSLwabIjnm0rc3x3A==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.0.2.tgz", + "integrity": "sha512-f/OCF9ThnxQ8m0eNYPwnCrySQPhYfCOF6STL7F9LnS8Bs3ZeW3/oT1yLaMIZ1Eg0ogIkgxksMAJZjrJPUPBD1Q==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0", - "@angular-eslint/utils": "19.0.0", + "@angular-eslint/bundled-angular-compiler": "19.0.2", + "@angular-eslint/utils": "19.0.2", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, @@ -927,17 +917,47 @@ "typescript": "*" } }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@angular-eslint/utils": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.2.tgz", + "integrity": "sha512-HotBT8OKr7zCaX1S9k27JuhRiTVIbbYVl6whlb3uwdMIPIWY8iOcEh1tjI4qDPUafpLfR72Dhwi5bO1E17F3/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.0.2" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@angular-eslint/utils": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.2.tgz", + "integrity": "sha512-HotBT8OKr7zCaX1S9k27JuhRiTVIbbYVl6whlb3uwdMIPIWY8iOcEh1tjI4qDPUafpLfR72Dhwi5bO1E17F3/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.0.2" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular-eslint/schematics": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.0.0.tgz", - "integrity": "sha512-fle4SMxjI+91y5eR6hVG7yhzJHAw87LudHw918hGUVn2INIAW1TTuuQNoah8kNg9I6ICIDat26IenD4nOau6Gg==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.0.2.tgz", + "integrity": "sha512-wI4SyiAnUCrpigtK6PHRlVWMC9vWljqmlLhbsJV5O5yDajlmRdvgXvSHDefhJm0hSfvZYRXuiAARYv2+QVfnGA==", "dev": true, "license": "MIT", "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", - "@angular-eslint/eslint-plugin": "19.0.0", - "@angular-eslint/eslint-plugin-template": "19.0.0", + "@angular-eslint/eslint-plugin": "19.0.2", + "@angular-eslint/eslint-plugin-template": "19.0.2", "ignore": "6.0.2", "semver": "7.6.3", "strip-json-comments": "3.1.1" @@ -954,13 +974,13 @@ } }, "node_modules/@angular-eslint/template-parser": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.0.0.tgz", - "integrity": "sha512-bOLMNBQbrLMujGWSda0SF8ka7snQ9Uzxie1dr5LquI104p2J4Wt90DOoaWzhNaBBwedt3WXmhSHmvvR9720kHA==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.0.2.tgz", + "integrity": "sha512-z3rZd2sBfuYcFf9rGDsB2zz2fbGX8kkF+0ftg9eocyQmzWrlZHFmuw9ha7oP/Mz8gpblyCS/aa1U/Srs6gz0UQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0", + "@angular-eslint/bundled-angular-compiler": "19.0.2", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -968,550 +988,10 @@ "typescript": "*" } }, - "node_modules/@angular-eslint/utils": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.0.tgz", - "integrity": "sha512-PH40BmIcIr5ldr08XYnqJ8cTzJfScJjBym4SECsilBnz5fhCdTD7UEQiW4d0P78Ie8H5PxvOJx9ZE+L4WBNrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular/build": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.0.4.tgz", - "integrity": "sha512-ubsNjLb54VkZwcPQ21Ke8aAHiIrRIcv7gG3R6/6XOoWeK1K2+tsv8bnO4mz5cHgzWOspLOT7FDC83NJjrKX3Nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1900.4", - "@babel/core": "7.26.0", - "@babel/helper-annotate-as-pure": "7.25.9", - "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-syntax-import-attributes": "7.26.0", - "@inquirer/confirm": "5.0.2", - "@vitejs/plugin-basic-ssl": "1.1.0", - "beasties": "0.1.0", - "browserslist": "^4.23.0", - "esbuild": "0.24.0", - "fast-glob": "3.3.2", - "https-proxy-agent": "7.0.5", - "istanbul-lib-instrument": "6.0.3", - "listr2": "8.2.5", - "magic-string": "0.30.12", - "mrmime": "2.0.0", - "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.2", - "piscina": "4.7.0", - "rollup": "4.26.0", - "sass": "1.80.7", - "semver": "7.6.3", - "vite": "5.4.11", - "watchpack": "2.4.2" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "optionalDependencies": { - "lmdb": "3.1.5" - }, - "peerDependencies": { - "@angular/compiler": "^19.0.0", - "@angular/compiler-cli": "^19.0.0", - "@angular/localize": "^19.0.0", - "@angular/platform-server": "^19.0.0", - "@angular/service-worker": "^19.0.0", - "@angular/ssr": "^19.0.4", - "less": "^4.2.0", - "postcss": "^8.4.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.5 <5.7" - }, - "peerDependenciesMeta": { - "@angular/localize": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "@angular/ssr": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tailwindcss": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" - } - }, - "node_modules/@angular/build/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/cdk": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.0.2.tgz", - "integrity": "sha512-eDjHJJWpgnzC3pR6N0gCdh51Q1ffoh6mql06YSqprj005aNKBjmCMnpU4bPPzdGSkKsjwAZWGUNWg4RS+R+iZQ==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.1.2.tgz", + "integrity": "sha512-rzrZ4BkGNIZWSdw0OsuSB/H9UB5ppPvmBq+uRHdYmZoYjo5wu1pmePxAIZDIBR8xdaNy9rZ4ecS6IebDkgYPrg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1526,26 +1006,26 @@ } }, "node_modules/@angular/cli": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.0.4.tgz", - "integrity": "sha512-jxnD9qkhelcRMCrHDCxNsWgn6HQCvMIj8uI0T2eB9Vy93q2YWUo/fWl2Sy4gFlR+VNeF+1hYhPLb/vqLLzjWuA==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.1.5.tgz", + "integrity": "sha512-bedjH3jUcrLgN3GOTTuvjbPcY3Lm0YcYBVY35S1ugI88UK6nbtttiRdgK++Qk2Q8wbg6zuaBAr4ACbfPMsnRaA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1900.4", - "@angular-devkit/core": "19.0.4", - "@angular-devkit/schematics": "19.0.4", - "@inquirer/prompts": "7.1.0", + "@angular-devkit/architect": "0.1901.5", + "@angular-devkit/core": "19.1.5", + "@angular-devkit/schematics": "19.1.5", + "@inquirer/prompts": "7.2.1", "@listr2/prompt-adapter-inquirer": "2.0.18", - "@schematics/angular": "19.0.4", + "@schematics/angular": "19.1.5", "@yarnpkg/lockfile": "1.1.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", "listr2": "8.2.5", - "npm-package-arg": "12.0.0", + "npm-package-arg": "12.0.1", "npm-pick-manifest": "10.0.0", "pacote": "20.0.0", - "resolve": "1.22.8", + "resolve": "1.22.10", "semver": "7.6.3", "symbol-observable": "4.0.0", "yargs": "17.7.2" @@ -1560,9 +1040,9 @@ } }, "node_modules/@angular/common": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.0.3.tgz", - "integrity": "sha512-YyBVZU+LQ38R+/U5vF/b1T3muROKpR0kkupMw7VKnGhQfgrRX5Dk3H2nr9ritt0zPc7TOUuQSlHMf3QWah2GDg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.1.4.tgz", + "integrity": "sha512-E4MCl13VIotOxmzKQ/UGciPeaRXQgH7ymesEjYVGcT8jmC+qz5dEcoN7L5Jvq9aUsmLBt9MFp/B5QqKCIXMqYA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1571,14 +1051,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "19.0.3", + "@angular/core": "19.1.4", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.0.3.tgz", - "integrity": "sha512-cxtK4SlHAPstcXfjwOaoR1dAszrzo2iDF8ZiihbZPgKUG3m27qIU3Lp5XBgxfZPlO4jh6TXkWznY7f6Tyxkb0Q==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.1.4.tgz", + "integrity": "sha512-9vGUZ+QhGWvf5dfeILybrh5rvZQtNqS8WumMeX2/vCb0JTA0N4DsL1Sy47HuWcgKBxbmHVUdF5/iufcFaqk2FA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1587,7 +1067,7 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "19.0.3" + "@angular/core": "19.1.4" }, "peerDependenciesMeta": { "@angular/core": { @@ -1596,9 +1076,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.0.3.tgz", - "integrity": "sha512-nayLcC3hSHoGKXCZInMdFcIZJEHYkEGNsdAutgCMuSj+lXCGuRUysuGC0rGzJc2R6nhgfaLJnO8T/O5acqaqdA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.1.4.tgz", + "integrity": "sha512-ozJvTUzPOgFqlz69YnV14Ncod+iH0cXZvUKerjw8o+JsixLG2LmJpwQ79Gh4a/ZQmAkAxMAYYK5izCiio8MmTg==", "license": "MIT", "dependencies": { "@babel/core": "7.26.0", @@ -1619,14 +1099,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "19.0.3", - "typescript": ">=5.5 <5.7" + "@angular/compiler": "19.1.4", + "typescript": ">=5.5 <5.8" } }, "node_modules/@angular/core": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.0.3.tgz", - "integrity": "sha512-WM844gDzrbHtcM2TJB9DmfCmenUYyNSI6h924CeppDW5oG8ShinQGiWNjF5oI6EZ4tG60uK3QvCm3kjr1dmbOA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.1.4.tgz", + "integrity": "sha512-r3T81lM9evmuW36HA3VAxIJ61M8kirGR8yHoln9fXSnYG8UeJ7JlWEbVRHmVHKOB48VK0bS/VxqN+w9TOq3bZg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1640,9 +1120,9 @@ } }, "node_modules/@angular/forms": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.0.3.tgz", - "integrity": "sha512-8wf8yDR6cW+lOhpzhmxUOiI5Wjr1Kf7o8NuJ2P5K6b7IMNRzRyR5q/6R4NUwtF6aaJ1wNqmSof+goQmtn1HOcw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.1.4.tgz", + "integrity": "sha512-dcf4G+vXrfvy5NAP+C4A2rBeaZuwKs/TeWjZDpkRUPQMwTvDJcSNH+pqOeVsYUGNY2BkY1uPjzmgZh4F5NMQ9A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1651,21 +1131,21 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.0.3", - "@angular/core": "19.0.3", - "@angular/platform-browser": "19.0.3", + "@angular/common": "19.1.4", + "@angular/core": "19.1.4", + "@angular/platform-browser": "19.1.4", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/localize": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-19.0.3.tgz", - "integrity": "sha512-xou8bCPpIn0h6GJm6isiV0qkzi7C/fnF5fC4ueiN/Bp6fOuRNdwTSwaTWz4RoWvgwbQs5eZ6yIKUb+9toUAOPw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-19.1.4.tgz", + "integrity": "sha512-AFfaxnGUWl1QZGmhYNTH8adWynSqjNwHweOUQ/ItIQ+MkbIPOpAtZp+ar6SRJZpatR59O8797jPKVFTAebLvLQ==", "license": "MIT", "dependencies": { "@babel/core": "7.26.0", "@types/babel__core": "7.20.5", - "fast-glob": "3.3.2", + "fast-glob": "3.3.3", "yargs": "^17.2.1" }, "bin": { @@ -1677,14 +1157,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "19.0.3", - "@angular/compiler-cli": "19.0.3" + "@angular/compiler": "19.1.4", + "@angular/compiler-cli": "19.1.4" } }, "node_modules/@angular/platform-browser": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.0.3.tgz", - "integrity": "sha512-vggWHSzOsCpYqnGq5IIN+n7xdEvXfgUGaMdgzPhFMTsnlMTUs5+VEFl9tX9FANHkXKB5S1RttVyvEXRqJM9ncQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.1.4.tgz", + "integrity": "sha512-IoVIvemj7ni6GLDCvwtZhTgMQjPyG+xPW7rASN2RVl9T3uS1fJUpXrh5JzBcCikIj20O2KV9mqt7p4iIXy9jbQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1693,9 +1173,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "19.0.3", - "@angular/common": "19.0.3", - "@angular/core": "19.0.3" + "@angular/animations": "19.1.4", + "@angular/common": "19.1.4", + "@angular/core": "19.1.4" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1704,9 +1184,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.0.3.tgz", - "integrity": "sha512-gFh+QN7JvepnD3mS0XmOtDmfY8h5sSkk2/guesE2A68Na8q+M3fGZlz7I37tCXToLth5us1X0Gi0UPCSESc4SA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.1.4.tgz", + "integrity": "sha512-r1AM8qkjl63cg46tgOHsVV4URHDctcVpt98DU/d/yN8JAugrx6GA1qOM/HMDspMjEIU4aYcSkUUY6h6uIkYmOQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1715,16 +1195,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.0.3", - "@angular/compiler": "19.0.3", - "@angular/core": "19.0.3", - "@angular/platform-browser": "19.0.3" + "@angular/common": "19.1.4", + "@angular/compiler": "19.1.4", + "@angular/core": "19.1.4", + "@angular/platform-browser": "19.1.4" } }, "node_modules/@angular/router": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.0.3.tgz", - "integrity": "sha512-L/s8crRC6nj5knmHsnPeOXMNdC7vUOSOvTQonXhmT0FdlP9bPnnRrNeVDnLnd8AzjPSBfIFE2eQw6T8jCwdxMA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.1.4.tgz", + "integrity": "sha512-0gEhGGqcCS7adKuv/XeQjRbhEqRXPhIH4ygjwfonV+uvmK+C1sf+bnAt4o01hxwf12w4FcnNPkgBKt+rJJ+LpA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1733,9 +1213,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.0.3", - "@angular/core": "19.0.3", - "@angular/platform-browser": "19.0.3", + "@angular/common": "19.1.4", + "@angular/core": "19.1.4", + "@angular/platform-browser": "19.1.4", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -1804,13 +1284,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -1988,10 +1468,11 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2015,15 +1496,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", - "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/traverse": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -2112,11 +1593,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -2501,13 +1983,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", - "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -2921,13 +2403,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", - "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -3234,13 +2716,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", + "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -3452,15 +2934,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -3468,10 +2951,27 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -3487,13 +2987,15 @@ "dev": true }, "node_modules/@codecov/bundler-plugin-core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.2.1.tgz", - "integrity": "sha512-9Iqr+PAQ/QVEnvKr56W/Jo8IaWJDFQkbcffNgG8Oc5GIQ1NRWsPmL/jxhkGrnp2LMJnpG2gPtV3+fet1jy6gbA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.8.0.tgz", + "integrity": "sha512-D1aeA8u3RHOkQVLImLHxW6zFdUrw5wgoeDzYrKZeDExGp5ePs4RpJxKwklg1N0e1JxRcAgKj+zZo/y3Q4nV+sA==", "dev": true, + "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", + "@sentry/core": "^8.42.0", "chalk": "4.1.2", "semver": "^7.5.4", "unplugin": "^1.10.1", @@ -3504,12 +3006,13 @@ } }, "node_modules/@codecov/webpack-plugin": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@codecov/webpack-plugin/-/webpack-plugin-1.2.1.tgz", - "integrity": "sha512-puA2Zb9A6e5sZ3AGpoYyymC64qgp0uUzDjnEa3EWtD/bQKa+m2HDu71sXUyJU/QFrWWa8xNaju4SPNVJU0m5EQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@codecov/webpack-plugin/-/webpack-plugin-1.8.0.tgz", + "integrity": "sha512-G62kbpAFuutQIrbuw97MAf5vkgIKXf64Nj0pRe6OPbC5Aps0nWrEL9v0Sr0WPdHcbQnr3i6noIXyUDdV1lUZ5g==", "dev": true, + "license": "MIT", "dependencies": { - "@codecov/bundler-plugin-core": "^1.2.1", + "@codecov/bundler-plugin-core": "^1.8.0", "unplugin": "^1.10.1" }, "engines": { @@ -3560,297 +3063,332 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", "cpu": [ - "x64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", "cpu": [ "arm64" ], @@ -3865,83 +3403,88 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3969,12 +3512,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -3983,19 +3527,24 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -4019,6 +3568,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4034,13 +3584,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -4053,6 +3605,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -4064,32 +3617,37 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", + "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, + "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -4101,6 +3659,7 @@ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" } @@ -4154,10 +3713,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.0.tgz", - "integrity": "sha512-xnRgu9DxZbkWak/te3fcytNyp8MTbuiZIaueg2rgEvBuN55n04nwLYLU9TX/VVlusc9L2ZNXi99nUFNkHXtr5g==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -4167,15 +3727,15 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.2.tgz", - "integrity": "sha512-+gznPl8ip8P8HYHYecDtUtdsh1t2jvb+sWCD72GAiZ9m45RqwrLmReDaqdC0umQfamtFXVRoMVJ2/qINKGm9Tg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.7.tgz", + "integrity": "sha512-lyoF4uYdBBTnqeB1gjPdYkiQ++fz/iYKaP9DON1ZGlldkvAEJsjaOBRdbl5UW1pOSslBRd701jxhAG0MlhHd2w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/figures": "^1.0.8", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/figures": "^1.0.10", + "@inquirer/type": "^3.0.3", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -4187,14 +3747,14 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.2.tgz", - "integrity": "sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.1.tgz", + "integrity": "sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1" + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" }, "engines": { "node": ">=18" @@ -4204,19 +3764,18 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.0.tgz", - "integrity": "sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.5.tgz", + "integrity": "sha512-/vyCWhET0ktav/mUeBqJRYTwmjFPIKPRYb3COAw7qORULgipGSUO2vL32lQKki3UxDKJ8BvuEbokaoyCA6YlWw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.8", - "@inquirer/type": "^3.0.1", + "@inquirer/figures": "^1.0.10", + "@inquirer/type": "^3.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, @@ -4238,14 +3797,14 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.1.0.tgz", - "integrity": "sha512-K1gGWsxEqO23tVdp5MT3H799OZ4ER1za7Dlc8F4um0W7lwSv0KGR/YyrUEyimj0g7dXZd8XknM/5QA2/Uy+TbA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.4.tgz", + "integrity": "sha512-S8b6+K9PLzxiFGGc02m4syhEu5JsH0BukzRsuZ+tpjJ5aDsDX1WfNfOil2fmsO36Y1RMcpJGxlfQ1yh4WfU28Q==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/type": "^3.0.3", "external-editor": "^3.1.0" }, "engines": { @@ -4256,14 +3815,14 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.2.tgz", - "integrity": "sha512-WdgCX1cUtinz+syKyZdJomovULYlKUWZbVYZzhf+ZeeYf4htAQ3jLymoNs3koIAKfZZl3HUBb819ClCBfyznaw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.7.tgz", + "integrity": "sha512-PsUQ5t7r+DPjW0VVEHzssOTBM2UPHnvBNse7hzuki7f6ekRL94drjjfBLrGEDe7cgj3pguufy/cuFwMeWUWHXw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/type": "^3.0.3", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -4274,9 +3833,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", - "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", + "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", "dev": true, "license": "MIT", "engines": { @@ -4284,14 +3843,14 @@ } }, "node_modules/@inquirer/input": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.0.2.tgz", - "integrity": "sha512-yCLCraigU085EcdpIVEDgyfGv4vBiE4I+k1qRkc9C5dMjWF42ADMGy1RFU94+eZlz4YlkmFsiyHZy0W1wdhaNg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.4.tgz", + "integrity": "sha512-CKKF8otRBdIaVnRxkFLs00VNA9HWlEh3x4SqUfC3A8819TeOZpTYG/p+4Nqu3hh97G+A0lxkOZNYE7KISgU8BA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1" + "@inquirer/core": "^10.1.5", + "@inquirer/type": "^3.0.3" }, "engines": { "node": ">=18" @@ -4301,14 +3860,14 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.2.tgz", - "integrity": "sha512-MKQhYofdUNk7eqJtz52KvM1dH6R93OMrqHduXCvuefKrsiMjHiMwjc3NZw5Imm2nqY7gWd9xdhYrtcHMJQZUxA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.7.tgz", + "integrity": "sha512-uU2nmXGC0kD8+BLgwZqcgBD1jcw2XFww2GmtP6b4504DkOp+fFAhydt7JzRR1TAI2dmj175p4SZB0lxVssNreA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1" + "@inquirer/core": "^10.1.5", + "@inquirer/type": "^3.0.3" }, "engines": { "node": ">=18" @@ -4318,14 +3877,14 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.2.tgz", - "integrity": "sha512-tQXGSu7IO07gsYlGy3VgXRVsbOWqFBMbqAUrJSc1PDTQQ5Qdm+QVwkP0OC0jnUZ62D19iPgXOMO+tnWG+HhjNQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.7.tgz", + "integrity": "sha512-DFpqWLx+C5GV5zeFWuxwDYaeYnTWYphO07pQ2VnP403RIqRIpwBG0ATWf7pF+3IDbaXEtWatCJWxyDrJ+rkj2A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/type": "^3.0.3", "ansi-escapes": "^4.3.2" }, "engines": { @@ -4336,22 +3895,22 @@ } }, "node_modules/@inquirer/prompts": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.1.0.tgz", - "integrity": "sha512-5U/XiVRH2pp1X6gpNAjWOglMf38/Ys522ncEHIKT1voRUvSj/DQnR22OVxHnwu5S+rCFaUiPQ57JOtMFQayqYA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.2.1.tgz", + "integrity": "sha512-v2JSGri6/HXSfoGIwuKEn8sNCQK6nsB2BNpy2lSX6QH9bsECrMv93QHnj5+f+1ZWpF/VNioIV2B/PDox8EvGuQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.0.2", - "@inquirer/confirm": "^5.0.2", - "@inquirer/editor": "^4.1.0", - "@inquirer/expand": "^4.0.2", - "@inquirer/input": "^4.0.2", - "@inquirer/number": "^3.0.2", - "@inquirer/password": "^4.0.2", - "@inquirer/rawlist": "^4.0.2", - "@inquirer/search": "^3.0.2", - "@inquirer/select": "^4.0.2" + "@inquirer/checkbox": "^4.0.4", + "@inquirer/confirm": "^5.1.1", + "@inquirer/editor": "^4.2.1", + "@inquirer/expand": "^4.0.4", + "@inquirer/input": "^4.1.1", + "@inquirer/number": "^3.0.4", + "@inquirer/password": "^4.0.4", + "@inquirer/rawlist": "^4.0.4", + "@inquirer/search": "^3.0.4", + "@inquirer/select": "^4.0.4" }, "engines": { "node": ">=18" @@ -4361,14 +3920,14 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.2.tgz", - "integrity": "sha512-3XGcskMoVF8H0Dl1S5TSZ3rMPPBWXRcM0VeNVsS4ByWeWjSeb0lPqfnBg6N7T0608I1B2bSVnbi2cwCrmOD1Yw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.7.tgz", + "integrity": "sha512-ZeBca+JCCtEIwQMvhuROT6rgFQWWvAImdQmIIP3XoyDFjrp2E0gZlEn65sWIoR6pP2EatYK96pvx0887OATWQQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/type": "^3.0.3", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -4379,15 +3938,15 @@ } }, "node_modules/@inquirer/search": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.2.tgz", - "integrity": "sha512-Zv4FC7w4dJ13BOJfKRQCICQfShinGjb1bCEIHxTSnjj2telu3+3RHwHubPG9HyD4aix5s+lyAMEK/wSFD75HLA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.7.tgz", + "integrity": "sha512-Krq925SDoLh9AWSNee8mbSIysgyWtcPnSAp5YtPBGCQ+OCO+5KGC8FwLpyxl8wZ2YAov/8Tp21stTRK/fw5SGg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/figures": "^1.0.8", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/figures": "^1.0.10", + "@inquirer/type": "^3.0.3", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -4398,15 +3957,15 @@ } }, "node_modules/@inquirer/select": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.2.tgz", - "integrity": "sha512-uSWUzaSYAEj0hlzxa1mUB6VqrKaYx0QxGBLZzU4xWFxaSyGaXxsSE4OSOwdU24j0xl8OajgayqFXW0l2bkl2kg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.7.tgz", + "integrity": "sha512-ejGBMDSD+Iqk60u5t0Zf2UQhGlJWDM78Ep70XpNufIfc+f4VOTeybYKXu9pDjz87FkRzLiVsGpQG2SzuGlhaJw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.0", - "@inquirer/figures": "^1.0.8", - "@inquirer/type": "^3.0.1", + "@inquirer/core": "^10.1.5", + "@inquirer/figures": "^1.0.10", + "@inquirer/type": "^3.0.3", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -4418,9 +3977,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.1.tgz", - "integrity": "sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.3.tgz", + "integrity": "sha512-I4VIHFxUuY1bshGbXZTxCmhwaaEst9s/lll3ekok+o1Z26/ZUKdx8y1b7lsoG6rtsBDwEGfiBJ2SfirjoISLpg==", "dev": true, "license": "MIT", "engines": { @@ -4942,9 +4501,9 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", - "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5028,9 +4587,9 @@ } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.1.5.tgz", - "integrity": "sha512-ue5PSOzHMCIYrfvPP/MRS6hsKKLzqqhcdAvJCO8uFlDdj598EhgnacuOTuqA6uBK5rgiZXfDWyb7DVZSiBKxBA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.2.2.tgz", + "integrity": "sha512-WBSJT9Z7DTol5viq+DZD2TapeWOw7mlwXxiSBHgAzqVwsaVb0h/ekMD9iu/jDD8MUA20tO9N0WEdnT06fsUp+g==", "cpu": [ "arm64" ], @@ -5042,9 +4601,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.1.5.tgz", - "integrity": "sha512-CGhsb0R5vE6mMNCoSfxHFD8QTvBHM51gs4DBeigTYHWnYv2V5YpJkC4rMo5qAAFifuUcc0+a8a3SIU0c9NrfNw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.2.2.tgz", + "integrity": "sha512-4S13kUtR7c/j/MzkTIBJCXv52hQ41LG2ukeaqw4Eng9K0pNKLFjo1sDSz96/yKhwykxrWDb13ddJ/ZqD3rAhUA==", "cpu": [ "x64" ], @@ -5056,9 +4615,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.1.5.tgz", - "integrity": "sha512-3WeW328DN+xB5PZdhSWmqE+t3+44xWXEbqQ+caWJEZfOFdLp9yklBZEbVqVdqzznkoaXJYxTCp996KD6HmANeg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.2.2.tgz", + "integrity": "sha512-uW31JmfuPAaLUYW7NsEU8gzwgDAzpGPwjvkxnKlcWd8iDutoPKDJi8Wk9lFmPEZRxVSB0j1/wDQ7N2qliR9UFA==", "cpu": [ "arm" ], @@ -5070,9 +4629,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.1.5.tgz", - "integrity": "sha512-LAjaoOcBHGj6fiYB8ureiqPoph4eygbXu4vcOF+hsxiY74n8ilA7rJMmGUT0K0JOB5lmRQHSmor3mytRjS4qeQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.2.2.tgz", + "integrity": "sha512-4hdgZtWI1idQlWRp+eleWXD9KLvObgboRaVoBj2POdPEYvsKANllvMW0El8tEQwtw74yB9NT6P8ENBB5UJf5+g==", "cpu": [ "arm64" ], @@ -5084,9 +4643,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.1.5.tgz", - "integrity": "sha512-k/IklElP70qdCXOQixclSl2GPLFiopynGoKX1FqDd1/H0E3Fo1oPwjY2rEVu+0nS3AOw1sryStdXk8CW3cVIsw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.2.2.tgz", + "integrity": "sha512-A0zjf4a2vM4B4GAx78ncuOTZ8Ka1DbTaG1Axf1e00Sa7f5coqlWiLg1PX7Gxvyibc2YqtqB+8tg1KKrE8guZVw==", "cpu": [ "x64" ], @@ -5098,9 +4657,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.1.5.tgz", - "integrity": "sha512-KYar6W8nraZfSJspcK7Kp7hdj238X/FNauYbZyrqPBrtsXI1hvI4/KcRcRGP50aQoV7fkKDyJERlrQGMGTZUsA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.2.2.tgz", + "integrity": "sha512-Y0qoSCAja+xZE7QQ0LCHoYAuyI1n9ZqukQJa8lv9X3yCvWahFF7OYHAgVH1ejp43XWstj3U89/PAAzcowgF/uQ==", "cpu": [ "x64" ], @@ -5622,9 +5181,9 @@ } }, "node_modules/@ng-select/ng-select": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-14.1.0.tgz", - "integrity": "sha512-cE/e7WIqLAgUF83mpmDWbgmy7OvzWTjCTjtcIzhabRbhN0RDqp7u39noC12kSN+viAfYnA1TS7rBru+IouNt1g==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-14.2.0.tgz", + "integrity": "sha512-Dq3PgOb0EBL31TV1Byr6RnQW/Vd5cdaaCEFbII2tIwiIT1r15oMdeSEZqzutuslBqgyggnponYAaBEgQBVorAg==", "license": "MIT", "dependencies": { "tslib": "^2.3.1" @@ -5655,9 +5214,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.0.4.tgz", - "integrity": "sha512-N3WCbQz5ipdAZoSWHNf81RLET6+isq35+GZu9u0StpFtJCpXAmRRAv4vdMUYL7DLOzRmvEgwww6Rd5AwGeLFSw==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.1.5.tgz", + "integrity": "sha512-oIpE5Ci/Gl2iZqa0Hs6IOxaXEDHkF/zisHcflzYGkMnYcSFj+wRgYEuBFaHLCwuxQf9OdGu31i05w849i6tY1Q==", "dev": true, "license": "MIT", "engines": { @@ -5667,7 +5226,7 @@ }, "peerDependencies": { "@angular/compiler-cli": "^19.0.0", - "typescript": ">=5.5 <5.7", + "typescript": ">=5.5 <5.8", "webpack": "^5.54.0" } }, @@ -6009,6 +5568,7 @@ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 18" } @@ -6018,6 +5578,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -6036,6 +5597,7 @@ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" @@ -6049,6 +5611,7 @@ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/request": "^8.3.0", "@octokit/types": "^13.0.0", @@ -6059,16 +5622,18 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "dev": true, + "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/types": "^12.6.0" }, @@ -6083,13 +5648,15 @@ "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/openapi-types": "^20.0.0" } @@ -6099,6 +5666,7 @@ "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/types": "^12.6.0" }, @@ -6113,13 +5681,15 @@ "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/openapi-types": "^20.0.0" } @@ -6129,6 +5699,7 @@ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/endpoint": "^9.0.1", "@octokit/request-error": "^5.1.0", @@ -6144,6 +5715,7 @@ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", @@ -6154,18 +5726,19 @@ } }, "node_modules/@octokit/types": { - "version": "13.6.1", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz", - "integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==", + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", + "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^22.2.0" + "@octokit/openapi-types": "^23.0.1" } }, "node_modules/@parcel/watcher": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", - "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6184,25 +5757,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.0", - "@parcel/watcher-darwin-arm64": "2.5.0", - "@parcel/watcher-darwin-x64": "2.5.0", - "@parcel/watcher-freebsd-x64": "2.5.0", - "@parcel/watcher-linux-arm-glibc": "2.5.0", - "@parcel/watcher-linux-arm-musl": "2.5.0", - "@parcel/watcher-linux-arm64-glibc": "2.5.0", - "@parcel/watcher-linux-arm64-musl": "2.5.0", - "@parcel/watcher-linux-x64-glibc": "2.5.0", - "@parcel/watcher-linux-x64-musl": "2.5.0", - "@parcel/watcher-win32-arm64": "2.5.0", - "@parcel/watcher-win32-ia32": "2.5.0", - "@parcel/watcher-win32-x64": "2.5.0" + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", - "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", "cpu": [ "arm64" ], @@ -6221,9 +5794,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", - "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", "cpu": [ "arm64" ], @@ -6242,9 +5815,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", - "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", "cpu": [ "x64" ], @@ -6263,9 +5836,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", - "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", "cpu": [ "x64" ], @@ -6284,9 +5857,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", - "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", "cpu": [ "arm" ], @@ -6305,9 +5878,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", - "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", "cpu": [ "arm" ], @@ -6326,9 +5899,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", - "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", "cpu": [ "arm64" ], @@ -6347,9 +5920,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", - "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", "cpu": [ "arm64" ], @@ -6368,9 +5941,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", - "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ "x64" ], @@ -6389,9 +5962,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", - "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ "x64" ], @@ -6410,9 +5983,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", - "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", "cpu": [ "arm64" ], @@ -6431,9 +6004,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", - "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", "cpu": [ "ia32" ], @@ -6452,9 +6025,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", - "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "cpu": [ "x64" ], @@ -6486,14 +6059,6 @@ "node": ">=0.10" } }, - "node_modules/@parcel/watcher/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -6506,12 +6071,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", - "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.48.2" + "playwright": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -6530,9 +6096,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.26.0.tgz", - "integrity": "sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.0.tgz", + "integrity": "sha512-Eeao7ewDq79jVEsrtWIj5RNqB8p2knlm9fhR6uJ2gqP7UfbLrTrxevudVrEPDM7Wkpn/HpRC2QfazH7MXLz3vQ==", "cpu": [ "arm" ], @@ -6544,9 +6110,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.26.0.tgz", - "integrity": "sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.0.tgz", + "integrity": "sha512-yVh0Kf1f0Fq4tWNf6mWcbQBCLDpDrDEl88lzPgKhrgTcDrTtlmun92ywEF9dCjmYO3EFiSuJeeo9cYRxl2FswA==", "cpu": [ "arm64" ], @@ -6558,9 +6124,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.26.0.tgz", - "integrity": "sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.0.tgz", + "integrity": "sha512-gCs0ErAZ9s0Osejpc3qahTsqIPUDjSKIyxK/0BGKvL+Tn0n3Kwvj8BrCv7Y5sR1Ypz1K2qz9Ny0VvkVyoXBVUQ==", "cpu": [ "arm64" ], @@ -6572,9 +6138,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.26.0.tgz", - "integrity": "sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.0.tgz", + "integrity": "sha512-aIB5Anc8hngk15t3GUkiO4pv42ykXHfmpXGS+CzM9CTyiWyT8HIS5ygRAy7KcFb/wiw4Br+vh1byqcHRTfq2tQ==", "cpu": [ "x64" ], @@ -6586,9 +6152,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.26.0.tgz", - "integrity": "sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.0.tgz", + "integrity": "sha512-kpdsUdMlVJMRMaOf/tIvxk8TQdzHhY47imwmASOuMajg/GXpw8GKNd8LNwIHE5Yd1onehNpcUB9jHY6wgw9nHQ==", "cpu": [ "arm64" ], @@ -6600,9 +6166,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.26.0.tgz", - "integrity": "sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.0.tgz", + "integrity": "sha512-D0RDyHygOBCQiqookcPevrvgEarN0CttBecG4chOeIYCNtlKHmf5oi5kAVpXV7qs0Xh/WO2RnxeicZPtT50V0g==", "cpu": [ "x64" ], @@ -6614,9 +6180,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.26.0.tgz", - "integrity": "sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.0.tgz", + "integrity": "sha512-mCIw8j5LPDXmCOW8mfMZwT6F/Kza03EnSr4wGYEswrEfjTfVsFOxvgYfuRMxTuUF/XmRb9WSMD5GhCWDe2iNrg==", "cpu": [ "arm" ], @@ -6628,9 +6194,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.26.0.tgz", - "integrity": "sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.0.tgz", + "integrity": "sha512-AwwldAu4aCJPob7zmjuDUMvvuatgs8B/QiVB0KwkUarAcPB3W+ToOT+18TQwY4z09Al7G0BvCcmLRop5zBLTag==", "cpu": [ "arm" ], @@ -6642,9 +6208,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.26.0.tgz", - "integrity": "sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.0.tgz", + "integrity": "sha512-e7kDUGVP+xw05pV65ZKb0zulRploU3gTu6qH1qL58PrULDGxULIS0OSDQJLH7WiFnpd3ZKUU4VM3u/Z7Zw+e7Q==", "cpu": [ "arm64" ], @@ -6656,9 +6222,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.26.0.tgz", - "integrity": "sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.0.tgz", + "integrity": "sha512-SXYJw3zpwHgaBqTXeAZ31qfW/v50wq4HhNVvKFhRr5MnptRX2Af4KebLWR1wpxGJtLgfS2hEPuALRIY3LPAAcA==", "cpu": [ "arm64" ], @@ -6669,10 +6235,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.0.tgz", + "integrity": "sha512-e5XiCinINCI4RdyU3sFyBH4zzz7LiQRvHqDtRe9Dt8o/8hTBaYpdPimayF00eY2qy5j4PaaWK0azRgUench6WQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.26.0.tgz", - "integrity": "sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.0.tgz", + "integrity": "sha512-3SWN3e0bAsm9ToprLFBSro8nJe6YN+5xmB11N4FfNf92wvLye/+Rh5JGQtKOpwLKt6e61R1RBc9g+luLJsc23A==", "cpu": [ "ppc64" ], @@ -6684,9 +6264,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.26.0.tgz", - "integrity": "sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.0.tgz", + "integrity": "sha512-B1Oqt3GLh7qmhvfnc2WQla4NuHlcxAD5LyueUi5WtMc76ZWY+6qDtQYqnxARx9r+7mDGfamD+8kTJO0pKUJeJA==", "cpu": [ "riscv64" ], @@ -6698,9 +6278,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.26.0.tgz", - "integrity": "sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.0.tgz", + "integrity": "sha512-UfUCo0h/uj48Jq2lnhX0AOhZPSTAq3Eostas+XZ+GGk22pI+Op1Y6cxQ1JkUuKYu2iU+mXj1QjPrZm9nNWV9rg==", "cpu": [ "s390x" ], @@ -6712,9 +6292,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz", - "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.0.tgz", + "integrity": "sha512-chZLTUIPbgcpm+Z7ALmomXW8Zh+wE2icrG+K6nt/HenPLmtwCajhQC5flNSk1Xy5EDMt/QAOz2MhzfOfJOLSiA==", "cpu": [ "x64" ], @@ -6726,9 +6306,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.26.0.tgz", - "integrity": "sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.0.tgz", + "integrity": "sha512-jo0UolK70O28BifvEsFD/8r25shFezl0aUk2t0VJzREWHkq19e+pcLu4kX5HiVXNz5qqkD+aAq04Ct8rkxgbyQ==", "cpu": [ "x64" ], @@ -6740,9 +6320,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.26.0.tgz", - "integrity": "sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.0.tgz", + "integrity": "sha512-Vmg0NhAap2S54JojJchiu5An54qa6t/oKT7LmDaWggpIcaiL8WcWHEN6OQrfTdL6mQ2GFyH7j2T5/3YPEDOOGA==", "cpu": [ "arm64" ], @@ -6754,9 +6334,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.26.0.tgz", - "integrity": "sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.0.tgz", + "integrity": "sha512-CV2aqhDDOsABKHKhNcs1SZFryffQf8vK2XrxP6lxC99ELZAdvsDgPklIBfd65R8R+qvOm1SmLaZ/Fdq961+m7A==", "cpu": [ "ia32" ], @@ -6768,9 +6348,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.26.0.tgz", - "integrity": "sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.0.tgz", + "integrity": "sha512-g2ASy1QwHP88y5KWvblUolJz9rN+i4ZOsYzkEwcNfaNooxNUXG+ON6F5xFo0NIItpHqxcdAyls05VXpBnludGw==", "cpu": [ "x64" ], @@ -6782,14 +6362,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.0.4.tgz", - "integrity": "sha512-1fXBtkA/AjgMPxHLpGlw7NuT/wggCqAwBAmDnSiRnBBV7Pgs/tHorLgh7A9eoUi3c8CYCuAh8zqWNyjBGGigOQ==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.1.5.tgz", + "integrity": "sha512-Yks2QD87z2qJhVLi6O0tQDBG4pyX5n5c8BYEyZ+yiThjzIXBRkHjWS1jIFvd/y1+yU/NQFHYG/sy8sVOxfQ9IA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.0.4", - "@angular-devkit/schematics": "19.0.4", + "@angular-devkit/core": "19.1.5", + "@angular-devkit/schematics": "19.1.5", "jsonc-parser": "3.3.1" }, "engines": { @@ -6798,6 +6378,16 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@sentry/core": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.53.0.tgz", + "integrity": "sha512-u6p5JeGSgvcoDqVcPve2gcJuhks8EQXPELzeYKuW3rHpsUfkLG6X5RVtk32dKOqqL2qzvMelnknBN7tyIf5PiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, "node_modules/@sigstore/bundle": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.0.0.tgz", @@ -6889,6 +6479,7 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -7118,9 +6709,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", - "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dev": true, "license": "MIT", "dependencies": { @@ -7164,6 +6755,7 @@ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7227,12 +6819,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.8.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz", - "integrity": "sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==", + "version": "22.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz", + "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" } }, "node_modules/@types/node-forge": { @@ -7246,9 +6839,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.17", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "dev": true, "license": "MIT" }, @@ -7322,9 +6915,9 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", - "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", "dev": true, "license": "MIT", "dependencies": { @@ -7347,20 +6940,21 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", - "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", + "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/type-utils": "8.12.2", - "@typescript-eslint/utils": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/type-utils": "8.22.0", + "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7371,24 +6965,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", - "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", + "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/typescript-estree": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4" }, "engines": { @@ -7399,22 +6990,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", - "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", + "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2" + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7425,15 +7013,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", - "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", + "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.12.2", - "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/utils": "8.22.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7442,17 +7031,17 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", - "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", + "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -7462,19 +7051,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", - "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", + "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7483,10 +7073,8 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -7494,6 +7082,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -7503,6 +7092,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -7514,15 +7104,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", - "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", + "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/typescript-estree": "8.12.2" + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7532,17 +7123,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", - "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", + "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.22.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7552,162 +7145,190 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", - "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz", + "integrity": "sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.6.0" + "node": ">=14.21.3" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -7715,13 +7336,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", @@ -7797,6 +7420,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -7815,6 +7439,7 @@ "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", "dev": true, + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" @@ -7828,6 +7453,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -7838,14 +7464,11 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -7871,6 +7494,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -7888,6 +7512,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -7900,6 +7525,7 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8033,6 +7659,13 @@ "dev": true, "license": "MIT" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -8067,6 +7700,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", @@ -8289,9 +7923,9 @@ "license": "MIT" }, "node_modules/beasties": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.1.0.tgz", - "integrity": "sha512-+Ssscd2gVG24qRNC+E2g88D+xsQW4xwakWtKAiGEQ3Pw54/FGdyo9RrfxhGhEv6ilFVbB7r3Lgx+QnAxnSpECw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.2.0.tgz", + "integrity": "sha512-Ljqskqx/tbZagIglYoJIMzH5zgssyp+in9+9sAyh15N22AornBeIDnb8EZ6Rk+6ShfMxd92uO3gfpT0NtZbpow==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -8299,23 +7933,28 @@ "css-what": "^6.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "htmlparser2": "^9.0.0", + "htmlparser2": "^9.1.0", "picocolors": "^1.1.1", - "postcss": "^8.4.47", + "postcss": "^8.4.49", "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -8444,9 +8083,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "funding": [ { "type": "opencollective", @@ -8461,10 +8100,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -8479,6 +8119,7 @@ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -8732,6 +8373,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -8751,9 +8423,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001676", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz", - "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==", + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", "funding": [ { "type": "opencollective", @@ -8767,7 +8439,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/canvas": { "version": "2.11.2", @@ -9237,6 +8910,7 @@ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, + "license": "MIT", "dependencies": { "is-what": "^3.14.1" }, @@ -9249,6 +8923,7 @@ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, + "license": "MIT", "dependencies": { "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", @@ -9273,6 +8948,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -9281,13 +8957,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", - "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.2" + "browserslist": "^4.24.3" }, "funding": { "type": "opencollective", @@ -9306,6 +8982,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -9331,13 +9008,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/cosmiconfig/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -9373,10 +9052,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -9391,6 +9071,7 @@ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, + "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -9456,6 +9137,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -9677,7 +9359,8 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/destroy": { "version": "1.2.0", @@ -9804,9 +9487,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9818,6 +9501,21 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -9832,10 +9530,27 @@ "dev": true, "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.50", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz", - "integrity": "sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==" + "version": "1.5.90", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", + "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", + "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", @@ -9859,6 +9574,7 @@ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -9965,6 +9681,7 @@ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "prr": "~1.0.1" @@ -9983,13 +9700,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -10009,48 +9724,64 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/esbuild-wasm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.24.0.tgz", - "integrity": "sha512-xhNn5tL1AhkPg4ft59yXT6FkwKXiPSYyz1IeinJHUJpjvOHOIPvdmFQc0pGdjxlKSbzZc2mNmtVOWAR1EF/JAg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.24.2.tgz", + "integrity": "sha512-03/7Z1gD+ohDnScFztvI4XddTAbKVmMEzCvvkBpQdWKEXJ+73dTyeNrmdxP1Q0zpDMFjzUJwtK4rLjqwiHbzkw==", "dev": true, "license": "MIT", "bin": { @@ -10107,26 +9838,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", + "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.19.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -10145,8 +9877,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -10303,6 +10034,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", @@ -10320,6 +10052,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -10396,7 +10129,8 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/events": { "version": "3.3.0", @@ -10558,15 +10292,16 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -10637,6 +10372,39 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -10762,6 +10530,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -10830,6 +10599,7 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, @@ -10949,16 +10719,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -10976,6 +10752,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -11045,6 +10835,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", @@ -11065,6 +10856,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -11073,12 +10865,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11124,23 +10917,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -11157,10 +10939,11 @@ "peer": true }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -11253,23 +11036,6 @@ "node": ">=12" } }, - "node_modules/html-entities": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -11328,9 +11094,9 @@ } }, "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", "dev": true, "license": "MIT" }, @@ -11339,6 +11105,7 @@ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -11379,6 +11146,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.15", "debug": "^4.3.6", @@ -11396,18 +11164,19 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -11451,6 +11220,7 @@ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -11531,6 +11301,7 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, + "license": "MIT", "optional": true, "bin": { "image-size": "bin/image-size.js" @@ -11688,12 +11459,16 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11862,7 +11637,8 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-wsl": { "version": "2.2.0", @@ -11997,6 +11773,25 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -12347,17 +12142,17 @@ } }, "node_modules/jest-preset-angular": { - "version": "14.4.2", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.4.2.tgz", - "integrity": "sha512-BYYv0FaTDfBNh8WyA9mpOV3krfw20kurBGK8INZUnv7KZDAWZuQtCET4TwTWxSNQ9jS1OX1+a5weCm/bTDDM1A==", + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.5.1.tgz", + "integrity": "sha512-HLYYMwNcv3mFrKbOPJwR29tKqVg+yWxez8ilCIsEj1HRYZ/OVsBy5+dcMok+VqL5nmeukTsGnEfGWt+SsQqtkA==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "esbuild-wasm": ">=0.15.13", - "jest-environment-jsdom": "^29.0.0", - "jest-util": "^29.0.0", - "pretty-format": "^29.0.0", + "jest-environment-jsdom": "^29.7.0", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0", "ts-jest": "^29.0.0" }, "engines": { @@ -12371,7 +12166,13 @@ "@angular/core": ">=15.0.0 <20.0.0", "@angular/platform-browser-dynamic": ">=15.0.0 <20.0.0", "jest": "^29.0.0", + "jsdom": ">=20.0.0", "typescript": ">=4.8" + }, + "peerDependenciesMeta": { + "jsdom": { + "optional": true + } } }, "node_modules/jest-regex-util": { @@ -12859,6 +12660,7 @@ "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", "dev": true, + "license": "MIT", "dependencies": { "source-map-support": "^0.5.5" } @@ -12911,10 +12713,11 @@ } }, "node_modules/less": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", - "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.1.tgz", + "integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -12941,6 +12744,7 @@ "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 18.12.0" }, @@ -12967,6 +12771,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "pify": "^4.0.1", @@ -12981,6 +12786,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -12991,6 +12797,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" @@ -13023,6 +12830,7 @@ "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", "dev": true, + "license": "ISC", "dependencies": { "webpack-sources": "^3.0.0" }, @@ -13146,9 +12954,9 @@ } }, "node_modules/lmdb": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.1.5.tgz", - "integrity": "sha512-46Mch5Drq+A93Ss3gtbg+Xuvf5BOgIuvhKDWoGa3HcPHI6BL2NCOkRdSx1D4VfzwrxhnsjbyIVsLRlQHu6URvw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.2.2.tgz", + "integrity": "sha512-LriG93la4PbmPMwI7Hbv8W+0ncLK7549w4sbZSi4QGDjnnxnmNMgxUkaQTEMzH8TpwsfFvgEjpLX7V8B/I9e3g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13164,14 +12972,22 @@ "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.1.5", - "@lmdb/lmdb-darwin-x64": "3.1.5", - "@lmdb/lmdb-linux-arm": "3.1.5", - "@lmdb/lmdb-linux-arm64": "3.1.5", - "@lmdb/lmdb-linux-x64": "3.1.5", - "@lmdb/lmdb-win32-x64": "3.1.5" + "@lmdb/lmdb-darwin-arm64": "3.2.2", + "@lmdb/lmdb-darwin-x64": "3.2.2", + "@lmdb/lmdb-linux-arm": "3.2.2", + "@lmdb/lmdb-linux-arm64": "3.2.2", + "@lmdb/lmdb-linux-x64": "3.2.2", + "@lmdb/lmdb-win32-x64": "3.2.2" } }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -13186,6 +13002,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -13225,7 +13042,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -13486,9 +13304,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -13553,6 +13371,16 @@ "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -13564,9 +13392,9 @@ } }, "node_modules/memfs": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.1.tgz", - "integrity": "sha512-Fq5CMEth+2iprLJ5mNizRcWuiwRZYjNkUD0zKk224jZunE9CRacTRDK8QLALbMBlNX2y3nY6lKZbesCwDwacig==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13645,6 +13473,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -14119,9 +13948,9 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -14129,6 +13958,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -14154,6 +13984,7 @@ "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.3", @@ -14171,6 +14002,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -14236,9 +14068,9 @@ } }, "node_modules/ngx-cookie-service": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-19.0.0.tgz", - "integrity": "sha512-itxGY1BlIRoEjEtDsSsRKnJuiQteTMLKPNHrykiH06tjUQ1bi3orE7YKU1D210VBqVy1jNrB7hKuGOOIQtQJDA==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-19.1.0.tgz", + "integrity": "sha512-EddQiinQ/EicUp4Lg/j0RwTsAJp7FCAGv+Z5dfwll/BoROb7hTnf7Suqbx2xZFNM4YqLXpZfXlAe8zig9cBfhw==", "license": "MIT", "dependencies": { "tslib": "^2.8.0" @@ -14248,6 +14080,19 @@ "@angular/core": "^19.0.0" } }, + "node_modules/ngx-device-detector": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-device-detector/-/ngx-device-detector-9.0.0.tgz", + "integrity": "sha512-zpio/wqH1GnxIpWCdA7cp5fmWf7YLycgzfXzQHmB9vaS7eAcqpBF5mxDS65IhE7TzNTJziDrYJCT+9tVqBcsDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0" + } + }, "node_modules/ngx-file-drop": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-16.0.0.tgz", @@ -14264,6 +14109,21 @@ "@angular/core": ">=14.0.0" } }, + "node_modules/ngx-ui-tour-core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-14.0.0.tgz", + "integrity": "sha512-6pzzEwxn/gCS3puEXDqgINBRbhvhzHYjmiA9DTCNEx1dPfYwjZVmPqNvNeZIVHucVnVZViAAKvA6MTc3Gm7aOw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@angular/router": "^19.0.0", + "rxjs": "^7.4.0" + } + }, "node_modules/ngx-ui-tour-ng-bootstrap": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/ngx-ui-tour-ng-bootstrap/-/ngx-ui-tour-ng-bootstrap-16.0.0.tgz", @@ -14279,21 +14139,6 @@ "@ng-bootstrap/ng-bootstrap": "^18.0.0" } }, - "node_modules/ngx-ui-tour-ng-bootstrap/node_modules/ngx-ui-tour-core": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-14.0.0.tgz", - "integrity": "sha512-6pzzEwxn/gCS3puEXDqgINBRbhvhzHYjmiA9DTCNEx1dPfYwjZVmPqNvNeZIVHucVnVZViAAKvA6MTc3Gm7aOw==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@angular/common": "^19.0.0", - "@angular/core": "^19.0.0", - "@angular/router": "^19.0.0", - "rxjs": "^7.4.0" - } - }, "node_modules/node-abi": { "version": "3.71.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", @@ -14308,10 +14153,9 @@ } }, "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT", "optional": true }, @@ -14580,9 +14424,10 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" }, "node_modules/nopt": { "version": "8.0.0", @@ -14629,6 +14474,7 @@ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14670,9 +14516,9 @@ } }, "node_modules/npm-package-arg": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.0.tgz", - "integrity": "sha512-ZTE0hbwSdTNL+Stx2zxSqdu2KZfNDcrtrLdIk7XGnQFYBWYDho/ORvXtn5XEePcL3tFpGjHCV3X3xrtDh7eZ+A==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.1.tgz", + "integrity": "sha512-aDxjFfPV3Liw0WOBWlyZLMBqtbgbg03rmGvHDJa2Ttv7tIz+1oB5qWec4psCDFZcZi9b5XdGkPdQiJxOPzvQRQ==", "dev": true, "license": "ISC", "dependencies": { @@ -15225,6 +15071,7 @@ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -15430,6 +15277,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -15476,13 +15324,6 @@ "node": "^18.12.0 || >= 20.9.0" } }, - "node_modules/pdfjs-dist/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT", - "optional": true - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15506,6 +15347,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=6" @@ -15521,9 +15363,9 @@ } }, "node_modules/piscina": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.7.0.tgz", - "integrity": "sha512-b8hvkpp9zS0zsfa939b/jXbe64Z2gZv0Ha7FYPNUiDIB1y2AtxcOZdfP8xN8HFjUaqQiT9gRlfjAsoL8vdJ1Iw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.8.0.tgz", + "integrity": "sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -15635,12 +15477,13 @@ } }, "node_modules/playwright": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz", - "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.48.2" + "playwright-core": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -15653,10 +15496,11 @@ } }, "node_modules/playwright-core": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", - "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -15670,6 +15514,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -15712,6 +15557,7 @@ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, + "license": "MIT", "dependencies": { "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", @@ -15750,6 +15596,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -15758,13 +15605,14 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, + "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -15775,12 +15623,13 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, + "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -15794,6 +15643,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, + "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, @@ -15805,10 +15655,11 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15821,7 +15672,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/prebuild-install": { "version": "7.1.2", @@ -16054,6 +15906,7 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/psl": { @@ -16286,7 +16139,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regexpu-core": { "version": "6.2.0", @@ -16350,18 +16204,22 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -16392,6 +16250,7 @@ "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", "dev": true, + "license": "MIT", "dependencies": { "adjust-sourcemap-loader": "^4.0.0", "convert-source-map": "^1.7.0", @@ -16408,6 +16267,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -16422,6 +16282,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -16492,9 +16353,9 @@ } }, "node_modules/rollup": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.26.0.tgz", - "integrity": "sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.0.tgz", + "integrity": "sha512-+4C/cgJ9w6sudisA0nZz0+O7lTP9a3CzNLsoDwaRumM8QHwghUsu6tqHXiTmNUp/rqNiM14++7dkzHDyCRs0Jg==", "dev": true, "license": "MIT", "dependencies": { @@ -16508,24 +16369,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.26.0", - "@rollup/rollup-android-arm64": "4.26.0", - "@rollup/rollup-darwin-arm64": "4.26.0", - "@rollup/rollup-darwin-x64": "4.26.0", - "@rollup/rollup-freebsd-arm64": "4.26.0", - "@rollup/rollup-freebsd-x64": "4.26.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", - "@rollup/rollup-linux-arm-musleabihf": "4.26.0", - "@rollup/rollup-linux-arm64-gnu": "4.26.0", - "@rollup/rollup-linux-arm64-musl": "4.26.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", - "@rollup/rollup-linux-riscv64-gnu": "4.26.0", - "@rollup/rollup-linux-s390x-gnu": "4.26.0", - "@rollup/rollup-linux-x64-gnu": "4.26.0", - "@rollup/rollup-linux-x64-musl": "4.26.0", - "@rollup/rollup-win32-arm64-msvc": "4.26.0", - "@rollup/rollup-win32-ia32-msvc": "4.26.0", - "@rollup/rollup-win32-x64-msvc": "4.26.0", + "@rollup/rollup-android-arm-eabi": "4.34.0", + "@rollup/rollup-android-arm64": "4.34.0", + "@rollup/rollup-darwin-arm64": "4.34.0", + "@rollup/rollup-darwin-x64": "4.34.0", + "@rollup/rollup-freebsd-arm64": "4.34.0", + "@rollup/rollup-freebsd-x64": "4.34.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.0", + "@rollup/rollup-linux-arm-musleabihf": "4.34.0", + "@rollup/rollup-linux-arm64-gnu": "4.34.0", + "@rollup/rollup-linux-arm64-musl": "4.34.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.0", + "@rollup/rollup-linux-riscv64-gnu": "4.34.0", + "@rollup/rollup-linux-s390x-gnu": "4.34.0", + "@rollup/rollup-linux-x64-gnu": "4.34.0", + "@rollup/rollup-linux-x64-musl": "4.34.0", + "@rollup/rollup-win32-arm64-msvc": "4.34.0", + "@rollup/rollup-win32-ia32-msvc": "4.34.0", + "@rollup/rollup-win32-x64-msvc": "4.34.0", "fsevents": "~2.3.2" } }, @@ -16599,9 +16461,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.80.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.7.tgz", - "integrity": "sha512-MVWvN0u5meytrSjsU7AWsbhoXi1sc58zADXFllfZzbsBT1GHjjar6JwBINYPRrkx/zqnQ6uqbQuHgE95O+C+eQ==", + "version": "1.83.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz", + "integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==", "dev": true, "license": "MIT", "dependencies": { @@ -16620,9 +16482,9 @@ } }, "node_modules/sass-loader": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.3.tgz", - "integrity": "sha512-gosNorT1RCkuCMyihv6FBRR7BMV06oKRAs+l4UMp1mlcVg9rWN6KMmUj3igjQwmYys4mDP3etEYJgiHRbgHCHA==", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz", + "integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==", "dev": true, "license": "MIT", "dependencies": { @@ -16665,6 +16527,7 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, + "license": "ISC", "optional": true }, "node_modules/saxes": { @@ -16680,10 +16543,11 @@ } }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -16691,7 +16555,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -16972,16 +16836,73 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -17180,6 +17101,7 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -17189,6 +17111,7 @@ "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, + "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" @@ -17209,6 +17132,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -17601,9 +17525,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -17745,12 +17669,6 @@ "node": ">=8" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -17868,36 +17786,40 @@ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, + "license": "MIT", "bin": { "tree-kill": "cli.js" } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-jest": { - "version": "29.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", - "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", "dev": true, + "license": "MIT", "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", "jest-util": "^29.0.0", "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" }, "bin": { "ts-jest": "cli.js" @@ -18024,6 +17946,7 @@ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } @@ -18092,7 +18015,8 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/typescript": { "version": "5.5.4", @@ -18120,10 +18044,11 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -18174,6 +18099,7 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -18211,7 +18137,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/universalify": { "version": "2.0.1", @@ -18233,24 +18160,17 @@ } }, "node_modules/unplugin": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.15.0.tgz", - "integrity": "sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", "dev": true, + "license": "MIT", "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" }, "engines": { "node": ">=14.0.0" - }, - "peerDependencies": { - "webpack-sources": "^3" - }, - "peerDependenciesMeta": { - "webpack-sources": { - "optional": true - } } }, "node_modules/update-browserslist-db": { @@ -18327,13 +18247,14 @@ } }, "node_modules/uuid": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", - "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/esm/bin/uuid" } @@ -18396,21 +18317,21 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", + "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -18419,19 +18340,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -18452,6 +18379,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -18527,17 +18460,17 @@ } }, "node_modules/webpack": { - "version": "5.96.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", - "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", @@ -18604,9 +18537,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz", - "integrity": "sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.0.tgz", + "integrity": "sha512-90SqqYXA2SK36KcT6o1bvwvZfJFcmoamqeJY7+boioffX9g9C0wjjJRGUrQIuh43pb0ttX7+ssavmj/WN2RHtA==", "dev": true, "license": "MIT", "dependencies": { @@ -18623,10 +18556,9 @@ "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", - "express": "^4.19.2", + "express": "^4.21.2", "graceful-fs": "^4.2.6", - "html-entities": "^2.4.0", - "http-proxy-middleware": "^2.0.3", + "http-proxy-middleware": "^2.0.7", "ipaddr.js": "^2.1.0", "launch-editor": "^2.6.1", "open": "^10.0.3", @@ -18765,6 +18697,7 @@ "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", "dev": true, + "license": "MIT", "dependencies": { "typed-assert": "^1.0.8" }, @@ -18785,7 +18718,8 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/webpack/node_modules/ajv": { "version": "6.12.6", @@ -19065,10 +18999,14 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -19133,10 +19071,11 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src-ui/package.json b/src-ui/package.json index f9d4d8158..1aecf9c06 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -11,17 +11,17 @@ }, "private": true, "dependencies": { - "@angular/cdk": "^19.0.2", - "@angular/common": "~19.0.3", - "@angular/compiler": "~19.0.3", - "@angular/core": "~19.0.3", - "@angular/forms": "~19.0.3", - "@angular/localize": "~19.0.3", - "@angular/platform-browser": "~19.0.3", - "@angular/platform-browser-dynamic": "~19.0.3", - "@angular/router": "~19.0.3", + "@angular/cdk": "^19.1.2", + "@angular/common": "~19.1.4", + "@angular/compiler": "~19.1.4", + "@angular/core": "~19.1.4", + "@angular/forms": "~19.1.4", + "@angular/localize": "~19.1.4", + "@angular/platform-browser": "~19.1.4", + "@angular/platform-browser-dynamic": "~19.1.4", + "@angular/router": "~19.1.4", "@ng-bootstrap/ng-bootstrap": "^18.0.0", - "@ng-select/ng-select": "^14.1.0", + "@ng-select/ng-select": "^14.2.0", "@ngneat/dirty-check-forms": "^3.0.3", "@popperjs/core": "^2.11.8", "bootstrap": "^5.3.3", @@ -30,36 +30,37 @@ "ng2-pdf-viewer": "^10.4.0", "ngx-bootstrap-icons": "^1.9.3", "ngx-color": "^9.0.0", - "ngx-cookie-service": "^19.0.0", + "ngx-cookie-service": "^19.1.0", + "ngx-device-detector": "^9.0.0", "ngx-file-drop": "^16.0.0", "ngx-ui-tour-ng-bootstrap": "^16.0.0", "rxjs": "^7.8.1", "tslib": "^2.8.1", "utif": "^3.1.0", - "uuid": "^11.0.2", + "uuid": "^11.0.5", "zone.js": "^0.15.0" }, "devDependencies": { - "@angular-builders/custom-webpack": "^19.0.0-beta.0", - "@angular-builders/jest": "^19.0.0-beta.1", + "@angular-builders/custom-webpack": "^19.0.0", + "@angular-builders/jest": "^19.0.0", "@angular-devkit/build-angular": "^19.0.4", - "@angular-devkit/core": "^19.0.4", - "@angular-devkit/schematics": "^19.0.4", - "@angular-eslint/builder": "19.0.0", - "@angular-eslint/eslint-plugin": "19.0.0", - "@angular-eslint/eslint-plugin-template": "19.0.0", - "@angular-eslint/schematics": "19.0.0", - "@angular-eslint/template-parser": "19.0.0", - "@angular/cli": "~19.0.4", - "@angular/compiler-cli": "~19.0.3", - "@codecov/webpack-plugin": "^1.2.1", - "@playwright/test": "^1.48.2", + "@angular-devkit/core": "^19.1.5", + "@angular-devkit/schematics": "^19.1.5", + "@angular-eslint/builder": "19.0.2", + "@angular-eslint/eslint-plugin": "19.0.2", + "@angular-eslint/eslint-plugin-template": "19.0.2", + "@angular-eslint/schematics": "19.0.2", + "@angular-eslint/template-parser": "19.0.2", + "@angular/cli": "~19.1.5", + "@angular/compiler-cli": "~19.1.4", + "@codecov/webpack-plugin": "^1.8.0", + "@playwright/test": "^1.50.1", "@types/jest": "^29.5.14", - "@types/node": "^22.8.6", - "@typescript-eslint/eslint-plugin": "^8.12.2", - "@typescript-eslint/parser": "^8.12.2", + "@types/node": "^22.13.0", + "@typescript-eslint/eslint-plugin": "^8.22.0", + "@typescript-eslint/parser": "^8.22.0", "@typescript-eslint/utils": "^8.0.0", - "eslint": "^9.14.0", + "eslint": "^9.19.0", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-preset-angular": "^14.4.2", diff --git a/src-ui/setup-jest.ts b/src-ui/setup-jest.ts index 244938606..52dccaf02 100644 --- a/src-ui/setup-jest.ts +++ b/src-ui/setup-jest.ts @@ -100,6 +100,15 @@ Object.defineProperty(navigator, 'clipboard', { }, }) Object.defineProperty(navigator, 'canShare', { value: () => true }) +if (!navigator.share) { + Object.defineProperty(navigator, 'share', { value: jest.fn() }) +} +if (!URL.createObjectURL) { + Object.defineProperty(window.URL, 'createObjectURL', { value: jest.fn() }) +} +if (!URL.revokeObjectURL) { + Object.defineProperty(window.URL, 'revokeObjectURL', { value: jest.fn() }) +} Object.defineProperty(window, 'ResizeObserver', { value: mock() }) Object.defineProperty(window, 'location', { configurable: true, diff --git a/src-ui/src/app/app.component.spec.ts b/src-ui/src/app/app.component.spec.ts index 74626f847..bc59f78dc 100644 --- a/src-ui/src/app/app.component.spec.ts +++ b/src-ui/src/app/app.component.spec.ts @@ -18,20 +18,20 @@ import { ToastsComponent } from './components/common/toasts/toasts.component' import { FileDropComponent } from './components/file-drop/file-drop.component' import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' import { PermissionsGuard } from './guards/permissions.guard' -import { - ConsumerStatusService, - FileStatus, -} from './services/consumer-status.service' import { HotKeyService } from './services/hot-key.service' import { PermissionsService } from './services/permissions.service' import { SettingsService } from './services/settings.service' import { Toast, ToastService } from './services/toast.service' +import { + FileStatus, + WebsocketStatusService, +} from './services/websocket-status.service' describe('AppComponent', () => { let component: AppComponent let fixture: ComponentFixture let tourService: TourService - let consumerStatusService: ConsumerStatusService + let websocketStatusService: WebsocketStatusService let permissionsService: PermissionsService let toastService: ToastService let router: Router @@ -59,7 +59,7 @@ describe('AppComponent', () => { }).compileComponents() tourService = TestBed.inject(TourService) - consumerStatusService = TestBed.inject(ConsumerStatusService) + websocketStatusService = TestBed.inject(WebsocketStatusService) permissionsService = TestBed.inject(PermissionsService) settingsService = TestBed.inject(SettingsService) toastService = TestBed.inject(ToastService) @@ -90,7 +90,7 @@ describe('AppComponent', () => { const toastSpy = jest.spyOn(toastService, 'show') const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentConsumptionFinished') + .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .mockReturnValue(fileStatusSubject) component.ngOnInit() const status = new FileStatus() @@ -109,7 +109,7 @@ describe('AppComponent', () => { const toastSpy = jest.spyOn(toastService, 'show') const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentConsumptionFinished') + .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .mockReturnValue(fileStatusSubject) component.ngOnInit() fileStatusSubject.next(new FileStatus()) @@ -122,7 +122,7 @@ describe('AppComponent', () => { const toastSpy = jest.spyOn(toastService, 'show') const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentDetected') + .spyOn(websocketStatusService, 'onDocumentDetected') .mockReturnValue(fileStatusSubject) component.ngOnInit() fileStatusSubject.next(new FileStatus()) @@ -136,7 +136,7 @@ describe('AppComponent', () => { const toastSpy = jest.spyOn(toastService, 'show') const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentDetected') + .spyOn(websocketStatusService, 'onDocumentDetected') .mockReturnValue(fileStatusSubject) component.ngOnInit() fileStatusSubject.next(new FileStatus()) @@ -148,7 +148,7 @@ describe('AppComponent', () => { const toastSpy = jest.spyOn(toastService, 'showError') const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentConsumptionFailed') + .spyOn(websocketStatusService, 'onDocumentConsumptionFailed') .mockReturnValue(fileStatusSubject) component.ngOnInit() fileStatusSubject.next(new FileStatus()) diff --git a/src-ui/src/app/app.component.ts b/src-ui/src/app/app.component.ts index d22b4ca38..a6c4702b7 100644 --- a/src-ui/src/app/app.component.ts +++ b/src-ui/src/app/app.component.ts @@ -5,7 +5,7 @@ import { first, Subscription } from 'rxjs' import { ToastsComponent } from './components/common/toasts/toasts.component' import { FileDropComponent } from './components/file-drop/file-drop.component' import { SETTINGS_KEYS } from './data/ui-settings' -import { ConsumerStatusService } from './services/consumer-status.service' +import { ComponentRouterService } from './services/component-router.service' import { HotKeyService } from './services/hot-key.service' import { PermissionAction, @@ -15,6 +15,7 @@ import { import { SettingsService } from './services/settings.service' import { TasksService } from './services/tasks.service' import { ToastService } from './services/toast.service' +import { WebsocketStatusService } from './services/websocket-status.service' @Component({ selector: 'pngx-root', @@ -34,14 +35,15 @@ export class AppComponent implements OnInit, OnDestroy { constructor( private settings: SettingsService, - private consumerStatusService: ConsumerStatusService, + private websocketStatusService: WebsocketStatusService, private toastService: ToastService, private router: Router, private tasksService: TasksService, public tourService: TourService, private renderer: Renderer2, private permissionsService: PermissionsService, - private hotKeyService: HotKeyService + private hotKeyService: HotKeyService, + private componentRouterService: ComponentRouterService ) { let anyWindow = window as any anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.mjs' @@ -49,7 +51,7 @@ export class AppComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.consumerStatusService.disconnect() + this.websocketStatusService.disconnect() if (this.successSubscription) { this.successSubscription.unsubscribe() } @@ -74,9 +76,9 @@ export class AppComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.consumerStatusService.connect() + this.websocketStatusService.connect() - this.successSubscription = this.consumerStatusService + this.successSubscription = this.websocketStatusService .onDocumentConsumptionFinished() .subscribe((status) => { this.tasksService.reload() @@ -106,7 +108,7 @@ export class AppComponent implements OnInit, OnDestroy { } }) - this.failedSubscription = this.consumerStatusService + this.failedSubscription = this.websocketStatusService .onDocumentConsumptionFailed() .subscribe((status) => { this.tasksService.reload() @@ -119,7 +121,7 @@ export class AppComponent implements OnInit, OnDestroy { } }) - this.newDocumentSubscription = this.consumerStatusService + this.newDocumentSubscription = this.websocketStatusService .onDocumentDetected() .subscribe((status) => { this.tasksService.reload() diff --git a/src-ui/src/app/components/admin/settings/settings.component.html b/src-ui/src/app/components/admin/settings/settings.component.html index 097015973..b8a46e57e 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.html +++ b/src-ui/src/app/components/admin/settings/settings.component.html @@ -41,7 +41,7 @@
-

Appearance

+
Appearance
Display language @@ -154,28 +154,7 @@
-

Document editing

- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
-

Update checking

+
Update checking
@@ -193,7 +172,56 @@
-

Bulk editing

+
Saved Views
+
+
+ +
+
+ +
+
+
Document editing
+ +
+
+ +
+
+ +
+
+ Default zoom: +
+
+ +

Only applies to the Paperless-ngx PDF viewer.

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
Notes
+
+
+ +
+
+ +
Bulk editing
@@ -201,7 +229,7 @@
-

Global search

+
Global search
@@ -224,19 +252,6 @@
-

Saved Views

-
-
- -
-
- -

Notes

-
-
- -
-
@@ -247,7 +262,7 @@ Permissions -

Default Permissions

+
Default Permissions
@@ -329,7 +344,7 @@ Notifications -

Document processing

+
Document processing
diff --git a/src-ui/src/app/components/admin/settings/settings.component.spec.ts b/src-ui/src/app/components/admin/settings/settings.component.spec.ts index 5f587cf9e..4f50e7453 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.spec.ts +++ b/src-ui/src/app/components/admin/settings/settings.component.spec.ts @@ -212,7 +212,7 @@ describe('SettingsComponent', () => { expect(toastErrorSpy).toHaveBeenCalled() expect(storeSpy).toHaveBeenCalled() expect(appearanceSettingsSpy).not.toHaveBeenCalled() - expect(setSpy).toHaveBeenCalledTimes(28) + expect(setSpy).toHaveBeenCalledTimes(29) // succeed storeSpy.mockReturnValueOnce(of(true)) diff --git a/src-ui/src/app/components/admin/settings/settings.component.ts b/src-ui/src/app/components/admin/settings/settings.component.ts index 9bd044f78..68f702cfa 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.ts +++ b/src-ui/src/app/components/admin/settings/settings.component.ts @@ -63,6 +63,7 @@ import { PermissionsUserComponent } from '../../common/input/permissions/permiss import { SelectComponent } from '../../common/input/select/select.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component' +import { ZoomSetting } from '../../document-detail/document-detail.component' import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' enum SettingsNavIDs { @@ -125,6 +126,7 @@ export class SettingsComponent defaultPermsEditUsers: new FormControl(null), defaultPermsEditGroups: new FormControl(null), useNativePdfViewer: new FormControl(null), + pdfViewerDefaultZoom: new FormControl(null), documentEditingRemoveInboxTags: new FormControl(null), documentEditingOverlayThumbnail: new FormControl(null), searchDbOnly: new FormControl(null), @@ -154,6 +156,8 @@ export class SettingsComponent public readonly GlobalSearchType = GlobalSearchType + public readonly ZoomSetting = ZoomSetting + get systemStatusHasErrors(): boolean { return ( this.systemStatus.database.status === SystemStatusItemStatus.ERROR || @@ -276,6 +280,9 @@ export class SettingsComponent useNativePdfViewer: this.settings.get( SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER ), + pdfViewerDefaultZoom: this.settings.get( + SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING + ), displayLanguage: this.settings.getLanguage(), dateLocale: this.settings.get(SETTINGS_KEYS.DATE_LOCALE), dateFormat: this.settings.get(SETTINGS_KEYS.DATE_FORMAT), @@ -435,6 +442,10 @@ export class SettingsComponent SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer ) + this.settings.set( + SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING, + this.settingsForm.value.pdfViewerDefaultZoom + ) this.settings.set( SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale diff --git a/src-ui/src/app/components/admin/trash/trash.component.ts b/src-ui/src/app/components/admin/trash/trash.component.ts index fcf43e1c1..1df6ceff4 100644 --- a/src-ui/src/app/components/admin/trash/trash.component.ts +++ b/src-ui/src/app/components/admin/trash/trash.component.ts @@ -86,12 +86,17 @@ export class TrashComponent modal.componentInstance.buttonsEnabled = false this.trashService.emptyTrash([document.id]).subscribe({ next: () => { - this.toastService.showInfo($localize`Document deleted`) + this.toastService.showInfo( + $localize`Document "${document.title}" deleted` + ) modal.close() this.reload() }, error: (err) => { - this.toastService.showError($localize`Error deleting document`, err) + this.toastService.showError( + $localize`Error deleting document "${document.title}"`, + err + ) modal.close() }, }) @@ -136,7 +141,7 @@ export class TrashComponent this.trashService.restoreDocuments([document.id]).subscribe({ next: () => { this.toastService.show({ - content: $localize`Document restored`, + content: $localize`Document "${document.title}" restored`, delay: 5000, actionName: $localize`Open document`, action: () => { @@ -146,7 +151,10 @@ export class TrashComponent this.reload() }, error: (err) => { - this.toastService.showError($localize`Error restoring document`, err) + this.toastService.showError( + $localize`Error restoring document "${document.title}"`, + err + ) }, }) } diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts index ab739aa54..559b03f51 100644 --- a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts @@ -134,7 +134,7 @@ describe('UsersAndGroupsComponent', () => { deleteSpy.mockReturnValueOnce(of(true)) deleteDialog.confirm() expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user') + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user "user1"') }) it('should logout current user if password changed, after delay', fakeAsync(() => { @@ -178,7 +178,7 @@ describe('UsersAndGroupsComponent', () => { completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.deleteGroup(users[0]) + component.deleteGroup(groups[0]) const deleteDialog = modal.componentInstance as ConfirmDialogComponent const deleteSpy = jest.spyOn(groupService, 'delete') const toastErrorSpy = jest.spyOn(toastService, 'showError') @@ -192,7 +192,7 @@ describe('UsersAndGroupsComponent', () => { deleteSpy.mockReturnValueOnce(of(true)) deleteDialog.confirm() expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group') + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group "group1"') }) it('should get group name', () => { diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts index 41ed58dac..9ed73cde4 100644 --- a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts @@ -129,13 +129,16 @@ export class UsersAndGroupsComponent this.usersService.delete(user).subscribe({ next: () => { modal.close() - this.toastService.showInfo($localize`Deleted user`) + this.toastService.showInfo($localize`Deleted user "${user.username}"`) this.usersService.listAll().subscribe((r) => { this.users = r.results }) }, error: (e) => { - this.toastService.showError($localize`Error deleting user.`, e) + this.toastService.showError( + $localize`Error deleting user "${user.username}".`, + e + ) }, }) }) @@ -179,13 +182,16 @@ export class UsersAndGroupsComponent this.groupsService.delete(group).subscribe({ next: () => { modal.close() - this.toastService.showInfo($localize`Deleted group`) + this.toastService.showInfo($localize`Deleted group "${group.name}"`) this.groupsService.listAll().subscribe((r) => { this.groups = r.results }) }, error: (e) => { - this.toastService.showError($localize`Error deleting group.`, e) + this.toastService.showError( + $localize`Error deleting group "${group.name}".`, + e + ) }, }) }) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index 442f9f366..b3d515274 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -30,12 +30,13 @@
    + diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.scss b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.scss new file mode 100644 index 000000000..2332e710d --- /dev/null +++ b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.scss @@ -0,0 +1,22 @@ +.dropdown-menu { + width: var(--pngx-toast-max-width); +} + +.dropdown-menu .scroll-list { + max-height: 500px; + overflow-y: auto; +} + +.dropdown-toggle::after { + display: none; +} + +.dropdown-item { + white-space: initial; +} + +@media screen and (max-width: 400px) { + :host ::ng-deep .dropdown-menu-end { + right: -3rem; + } +} diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.spec.ts b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.spec.ts new file mode 100644 index 000000000..33b948f30 --- /dev/null +++ b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.spec.ts @@ -0,0 +1,112 @@ +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { provideHttpClientTesting } from '@angular/common/http/testing' +import { + ComponentFixture, + TestBed, + discardPeriodicTasks, + fakeAsync, + flush, +} from '@angular/core/testing' +import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { Subject } from 'rxjs' +import { Toast, ToastService } from 'src/app/services/toast.service' +import { ToastsDropdownComponent } from './toasts-dropdown.component' + +const toasts = [ + { + id: 'abc-123', + content: 'foo bar', + delay: 5000, + }, + { + id: 'def-123', + content: 'Error 1 content', + delay: 5000, + error: 'Error 1 string', + }, + { + id: 'ghi-123', + content: 'Error 2 content', + delay: 5000, + error: { + url: 'https://example.com', + status: 500, + statusText: 'Internal Server Error', + message: 'Internal server error 500 message', + error: { detail: 'Error 2 message details' }, + }, + }, +] + +describe('ToastsDropdownComponent', () => { + let component: ToastsDropdownComponent + let fixture: ComponentFixture + let toastService: ToastService + let toastsSubject: Subject = new Subject() + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [ + ToastsDropdownComponent, + NgxBootstrapIconsModule.pick(allIcons), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + ], + }).compileComponents() + + fixture = TestBed.createComponent(ToastsDropdownComponent) + toastService = TestBed.inject(ToastService) + jest.spyOn(toastService, 'getToasts').mockReturnValue(toastsSubject) + + component = fixture.componentInstance + + fixture.detectChanges() + }) + + it('should call getToasts and return toasts', fakeAsync(() => { + const spy = jest.spyOn(toastService, 'getToasts') + + component.ngOnInit() + toastsSubject.next(toasts) + fixture.detectChanges() + + expect(spy).toHaveBeenCalled() + expect(component.toasts).toContainEqual({ + id: 'abc-123', + content: 'foo bar', + delay: 5000, + }) + + component.ngOnDestroy() + flush() + discardPeriodicTasks() + })) + + it('should show a toast', fakeAsync(() => { + component.ngOnInit() + toastsSubject.next(toasts) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('foo bar') + + component.ngOnDestroy() + flush() + discardPeriodicTasks() + })) + + it('should toggle suppressPopupToasts', fakeAsync((finish) => { + component.ngOnInit() + fixture.detectChanges() + toastsSubject.next(toasts) + + const spy = jest.spyOn(toastService, 'suppressPopupToasts', 'set') + component.onOpenChange(true) + expect(spy).toHaveBeenCalledWith(true) + + component.ngOnDestroy() + flush() + discardPeriodicTasks() + })) +}) diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts new file mode 100644 index 000000000..c04d758af --- /dev/null +++ b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.ts @@ -0,0 +1,42 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { + NgbDropdownModule, + NgbProgressbarModule, +} from '@ng-bootstrap/ng-bootstrap' +import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' +import { Subscription } from 'rxjs' +import { Toast, ToastService } from 'src/app/services/toast.service' +import { ToastComponent } from '../../common/toast/toast.component' + +@Component({ + selector: 'pngx-toasts-dropdown', + templateUrl: './toasts-dropdown.component.html', + styleUrls: ['./toasts-dropdown.component.scss'], + imports: [ + ToastComponent, + NgbDropdownModule, + NgbProgressbarModule, + NgxBootstrapIconsModule, + ], +}) +export class ToastsDropdownComponent implements OnInit, OnDestroy { + constructor(public toastService: ToastService) {} + + private subscription: Subscription + + public toasts: Toast[] = [] + + ngOnDestroy(): void { + this.subscription?.unsubscribe() + } + + ngOnInit(): void { + this.subscription = this.toastService.getToasts().subscribe((toasts) => { + this.toasts = [...toasts] + }) + } + + onOpenChange(open: boolean): void { + this.toastService.suppressPopupToasts = open + } +} diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html index 2f119b074..742dd8e8a 100644 --- a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html @@ -29,10 +29,17 @@ + #d="ngbDatepicker" + [footerTemplate]="datePickerFooterTemplate" /> + +
    + + +
    +
    } @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Float || getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Integer) { } @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Boolean) { diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss index f38bb4002..2f9e2c45e 100644 --- a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss @@ -41,3 +41,9 @@ min-width: 140px; } } + +.btn-group-xs { + > .btn { + border-radius: 0.15rem; + } +} diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts index 0fa7fe536..324fd27a5 100644 --- a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts @@ -241,6 +241,8 @@ export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPerm customFields: CustomField[] = [] + public readonly today: string = new Date().toISOString().split('T')[0] + constructor(protected customFieldsService: CustomFieldsService) { super() this.selectionModel = new CustomFieldQueriesModel() diff --git a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html index dcab4606d..c3ff61ba8 100644 --- a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html +++ b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html @@ -1,5 +1,5 @@
    - + +
    + + +
    +
@@ -95,40 +107,52 @@ diff --git a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.scss b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.scss index e101a131d..ebd34b29a 100644 --- a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.scss +++ b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.scss @@ -41,3 +41,9 @@ } } } + +.btn-group-xs { + > .btn { + border-radius: 0.15rem; + } +} diff --git a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.spec.ts b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.spec.ts index 10762264a..1f6ee909e 100644 --- a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.spec.ts +++ b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.spec.ts @@ -61,7 +61,7 @@ describe('DatesDropdownComponent', () => { it('should support date input, emit change', fakeAsync(() => { let result: string - component.createdDateAfterChange.subscribe((date) => (result = date)) + component.createdDateFromChange.subscribe((date) => (result = date)) const input: HTMLInputElement = fixture.nativeElement.querySelector('input') input.value = '5/30/2023' input.dispatchEvent(new Event('change')) @@ -83,68 +83,68 @@ describe('DatesDropdownComponent', () => { let result: DateSelection component.datesSet.subscribe((date) => (result = date)) component.setCreatedRelativeDate(null) - component.setCreatedRelativeDate(RelativeDate.LAST_7_DAYS) + component.setCreatedRelativeDate(RelativeDate.WITHIN_1_WEEK) component.setAddedRelativeDate(null) - component.setAddedRelativeDate(RelativeDate.LAST_7_DAYS) + component.setAddedRelativeDate(RelativeDate.WITHIN_1_WEEK) tick(500) expect(result).toEqual({ - createdAfter: null, - createdBefore: null, - createdRelativeDateID: RelativeDate.LAST_7_DAYS, - addedAfter: null, - addedBefore: null, - addedRelativeDateID: RelativeDate.LAST_7_DAYS, + createdFrom: null, + createdTo: null, + createdRelativeDateID: RelativeDate.WITHIN_1_WEEK, + addedFrom: null, + addedTo: null, + addedRelativeDateID: RelativeDate.WITHIN_1_WEEK, }) })) it('should support report if active', () => { - component.createdRelativeDate = RelativeDate.LAST_7_DAYS + component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK expect(component.isActive).toBeTruthy() component.createdRelativeDate = null - component.createdDateAfter = '2023-05-30' + component.createdDateFrom = '2023-05-30' expect(component.isActive).toBeTruthy() - component.createdDateAfter = null - component.createdDateBefore = '2023-05-30' + component.createdDateFrom = null + component.createdDateTo = '2023-05-30' expect(component.isActive).toBeTruthy() - component.createdDateBefore = null + component.createdDateTo = null - component.addedRelativeDate = RelativeDate.LAST_7_DAYS + component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK expect(component.isActive).toBeTruthy() component.addedRelativeDate = null - component.addedDateAfter = '2023-05-30' + component.addedDateFrom = '2023-05-30' expect(component.isActive).toBeTruthy() - component.addedDateAfter = null - component.addedDateBefore = '2023-05-30' + component.addedDateFrom = null + component.addedDateTo = '2023-05-30' expect(component.isActive).toBeTruthy() - component.addedDateBefore = null + component.addedDateTo = null expect(component.isActive).toBeFalsy() }) it('should support reset', () => { - component.createdDateAfter = '2023-05-30' + component.createdDateFrom = '2023-05-30' component.reset() - expect(component.createdDateAfter).toBeNull() + expect(component.createdDateFrom).toBeNull() }) - it('should support clearAfter', () => { - component.createdDateAfter = '2023-05-30' - component.clearCreatedAfter() - expect(component.createdDateAfter).toBeNull() + it('should support clearFrom', () => { + component.createdDateFrom = '2023-05-30' + component.clearCreatedFrom() + expect(component.createdDateFrom).toBeNull() - component.addedDateAfter = '2023-05-30' - component.clearAddedAfter() - expect(component.addedDateAfter).toBeNull() + component.addedDateFrom = '2023-05-30' + component.clearAddedFrom() + expect(component.addedDateFrom).toBeNull() }) - it('should support clearBefore', () => { - component.createdDateBefore = '2023-05-30' - component.clearCreatedBefore() - expect(component.createdDateBefore).toBeNull() + it('should support clearTo', () => { + component.createdDateTo = '2023-05-30' + component.clearCreatedTo() + expect(component.createdDateTo).toBeNull() - component.addedDateBefore = '2023-05-30' - component.clearAddedBefore() - expect(component.addedDateBefore).toBeNull() + component.addedDateTo = '2023-05-30' + component.clearAddedTo() + expect(component.addedDateTo).toBeNull() }) it('should limit keyboard events', () => { diff --git a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts index 356ba510a..e7d506d18 100644 --- a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts +++ b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts @@ -23,19 +23,19 @@ import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-optio import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component' export interface DateSelection { - createdBefore?: string - createdAfter?: string + createdTo?: string + createdFrom?: string createdRelativeDateID?: number - addedBefore?: string - addedAfter?: string + addedTo?: string + addedFrom?: string addedRelativeDateID?: number } export enum RelativeDate { - LAST_7_DAYS = 0, - LAST_MONTH = 1, - LAST_3_MONTHS = 2, - LAST_YEAR = 3, + WITHIN_1_WEEK = 0, + WITHIN_1_MONTH = 1, + WITHIN_3_MONTHS = 2, + WITHIN_1_YEAR = 3, } @Component({ @@ -63,23 +63,23 @@ export class DatesDropdownComponent implements OnInit, OnDestroy { relativeDates = [ { - id: RelativeDate.LAST_7_DAYS, - name: $localize`Last 7 days`, + id: RelativeDate.WITHIN_1_WEEK, + name: $localize`Within 1 week`, date: new Date().setDate(new Date().getDate() - 7), }, { - id: RelativeDate.LAST_MONTH, - name: $localize`Last month`, + id: RelativeDate.WITHIN_1_MONTH, + name: $localize`Within 1 month`, date: new Date().setMonth(new Date().getMonth() - 1), }, { - id: RelativeDate.LAST_3_MONTHS, - name: $localize`Last 3 months`, + id: RelativeDate.WITHIN_3_MONTHS, + name: $localize`Within 3 months`, date: new Date().setMonth(new Date().getMonth() - 3), }, { - id: RelativeDate.LAST_YEAR, - name: $localize`Last year`, + id: RelativeDate.WITHIN_1_YEAR, + name: $localize`Within 1 year`, date: new Date().setFullYear(new Date().getFullYear() - 1), }, ] @@ -88,16 +88,16 @@ export class DatesDropdownComponent implements OnInit, OnDestroy { // created @Input() - createdDateBefore: string + createdDateTo: string @Output() - createdDateBeforeChange = new EventEmitter() + createdDateToChange = new EventEmitter() @Input() - createdDateAfter: string + createdDateFrom: string @Output() - createdDateAfterChange = new EventEmitter() + createdDateFromChange = new EventEmitter() @Input() createdRelativeDate: RelativeDate @@ -107,16 +107,16 @@ export class DatesDropdownComponent implements OnInit, OnDestroy { // added @Input() - addedDateBefore: string + addedDateTo: string @Output() - addedDateBeforeChange = new EventEmitter() + addedDateToChange = new EventEmitter() @Input() - addedDateAfter: string + addedDateFrom: string @Output() - addedDateAfterChange = new EventEmitter() + addedDateFromChange = new EventEmitter() @Input() addedRelativeDate: RelativeDate @@ -133,14 +133,16 @@ export class DatesDropdownComponent implements OnInit, OnDestroy { @Input() disabled: boolean = false + public readonly today: string = new Date().toISOString().split('T')[0] + get isActive(): boolean { return ( this.createdRelativeDate !== null || - this.createdDateAfter?.length > 0 || - this.createdDateBefore?.length > 0 || + this.createdDateFrom?.length > 0 || + this.createdDateTo?.length > 0 || this.addedRelativeDate !== null || - this.addedDateAfter?.length > 0 || - this.addedDateBefore?.length > 0 + this.addedDateFrom?.length > 0 || + this.addedDateTo?.length > 0 ) } @@ -161,42 +163,42 @@ export class DatesDropdownComponent implements OnInit, OnDestroy { } reset() { - this.createdDateBefore = null - this.createdDateAfter = null + this.createdDateTo = null + this.createdDateFrom = null this.createdRelativeDate = null - this.addedDateBefore = null - this.addedDateAfter = null + this.addedDateTo = null + this.addedDateFrom = null this.addedRelativeDate = null this.onChange() } setCreatedRelativeDate(rd: RelativeDate) { - this.createdDateBefore = null - this.createdDateAfter = null + this.createdDateTo = null + this.createdDateFrom = null this.createdRelativeDate = this.createdRelativeDate == rd ? null : rd this.onChange() } setAddedRelativeDate(rd: RelativeDate) { - this.addedDateBefore = null - this.addedDateAfter = null + this.addedDateTo = null + this.addedDateFrom = null this.addedRelativeDate = this.addedRelativeDate == rd ? null : rd this.onChange() } onChange() { - this.createdDateBeforeChange.emit(this.createdDateBefore) - this.createdDateAfterChange.emit(this.createdDateAfter) + this.createdDateToChange.emit(this.createdDateTo) + this.createdDateFromChange.emit(this.createdDateFrom) this.createdRelativeDateChange.emit(this.createdRelativeDate) - this.addedDateBeforeChange.emit(this.addedDateBefore) - this.addedDateAfterChange.emit(this.addedDateAfter) + this.addedDateToChange.emit(this.addedDateTo) + this.addedDateFromChange.emit(this.addedDateFrom) this.addedRelativeDateChange.emit(this.addedRelativeDate) this.datesSet.emit({ - createdAfter: this.createdDateAfter, - createdBefore: this.createdDateBefore, + createdFrom: this.createdDateFrom, + createdTo: this.createdDateTo, createdRelativeDateID: this.createdRelativeDate, - addedAfter: this.addedDateAfter, - addedBefore: this.addedDateBefore, + addedFrom: this.addedDateFrom, + addedTo: this.addedDateTo, addedRelativeDateID: this.addedRelativeDate, }) } @@ -205,30 +207,30 @@ export class DatesDropdownComponent implements OnInit, OnDestroy { this.createdRelativeDate = null this.addedRelativeDate = null this.datesSetDebounce$.next({ - createdAfter: this.createdDateAfter, - createdBefore: this.createdDateBefore, - addedAfter: this.addedDateAfter, - addedBefore: this.addedDateBefore, + createdAfter: this.createdDateFrom, + createdBefore: this.createdDateTo, + addedAfter: this.addedDateFrom, + addedBefore: this.addedDateTo, }) } - clearCreatedBefore() { - this.createdDateBefore = null + clearCreatedTo() { + this.createdDateTo = null this.onChange() } - clearCreatedAfter() { - this.createdDateAfter = null + clearCreatedFrom() { + this.createdDateFrom = null this.onChange() } - clearAddedBefore() { - this.addedDateBefore = null + clearAddedTo() { + this.addedDateTo = null this.onChange() } - clearAddedAfter() { - this.addedDateAfter = null + clearAddedFrom() { + this.addedDateFrom = null this.onChange() } diff --git a/src-ui/src/app/components/common/input/date/date.component.html b/src-ui/src/app/components/common/input/date/date.component.html index 8f386e2c8..3221677fc 100644 --- a/src-ui/src/app/components/common/input/date/date.component.html +++ b/src-ui/src/app/components/common/input/date/date.component.html @@ -12,10 +12,16 @@
+ name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel" [disabled]="disabled" [footerTemplate]="datePickerFooterTemplate"> + +
+ + +
+
@if (showFilter) { +
+
+ + + } + @if (toast.action) { +

+ } + + + + diff --git a/src-ui/src/app/components/common/toast/toast.component.scss b/src-ui/src/app/components/common/toast/toast.component.scss new file mode 100644 index 000000000..3783445de --- /dev/null +++ b/src-ui/src/app/components/common/toast/toast.component.scss @@ -0,0 +1,20 @@ +::ng-deep .toast-body { + position: relative; +} + +::ng-deep .toast.error { + border-color: hsla(350, 79%, 40%, 0.4); // bg-danger +} + +::ng-deep .toast.error .toast-body { + background-color: hsla(350, 79%, 40%, 0.8); // bg-danger + border-top-left-radius: inherit; + border-top-right-radius: inherit; + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; +} + +.progress { + background-color: var(--pngx-primary); + opacity: .07; +} diff --git a/src-ui/src/app/components/common/toast/toast.component.spec.ts b/src-ui/src/app/components/common/toast/toast.component.spec.ts new file mode 100644 index 000000000..c5d52a28f --- /dev/null +++ b/src-ui/src/app/components/common/toast/toast.component.spec.ts @@ -0,0 +1,104 @@ +import { + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + flush, + TestBed, + tick, +} from '@angular/core/testing' + +import { Clipboard } from '@angular/cdk/clipboard' +import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' +import { ToastComponent } from './toast.component' + +const toast1 = { + content: 'Error 1 content', + delay: 5000, + error: 'Error 1 string', +} + +const toast2 = { + content: 'Error 2 content', + delay: 5000, + error: { + url: 'https://example.com', + status: 500, + statusText: 'Internal Server Error', + message: 'Internal server error 500 message', + error: { detail: 'Error 2 message details' }, + }, +} + +describe('ToastComponent', () => { + let component: ToastComponent + let fixture: ComponentFixture + let clipboard: Clipboard + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ToastComponent, NgxBootstrapIconsModule.pick(allIcons)], + }).compileComponents() + + fixture = TestBed.createComponent(ToastComponent) + clipboard = TestBed.inject(Clipboard) + component = fixture.componentInstance + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('should countdown toast', fakeAsync(() => { + component.toast = toast2 + fixture.detectChanges() + component.onShown(toast2) + tick(5000) + expect(component.toast.delayRemaining).toEqual(0) + flush() + discardPeriodicTasks() + })) + + it('should show an error if given with toast', fakeAsync(() => { + component.toast = toast1 + fixture.detectChanges() + + expect(fixture.nativeElement.querySelector('details')).not.toBeNull() + expect(fixture.nativeElement.textContent).toContain('Error 1 content') + + flush() + discardPeriodicTasks() + })) + + it('should show error details, support copy', fakeAsync(() => { + component.toast = toast2 + fixture.detectChanges() + + expect(fixture.nativeElement.querySelector('details')).not.toBeNull() + expect(fixture.nativeElement.textContent).toContain( + 'Error 2 message details' + ) + + const copySpy = jest.spyOn(clipboard, 'copy') + component.copyError(toast2.error) + expect(copySpy).toHaveBeenCalled() + + flush() + discardPeriodicTasks() + })) + + it('should parse error text, add ellipsis', () => { + expect(component.getErrorText(toast2.error)).toEqual( + 'Error 2 message details' + ) + expect(component.getErrorText({ error: 'Error string no detail' })).toEqual( + 'Error string no detail' + ) + expect(component.getErrorText('Error string')).toEqual('') + expect( + component.getErrorText({ error: { message: 'foo error bar' } }) + ).toContain('{"message":"foo error bar"}') + expect( + component.getErrorText({ error: new Array(205).join('a') }) + ).toContain('...') + }) +}) diff --git a/src-ui/src/app/components/common/toast/toast.component.ts b/src-ui/src/app/components/common/toast/toast.component.ts new file mode 100644 index 000000000..5ebfdbe82 --- /dev/null +++ b/src-ui/src/app/components/common/toast/toast.component.ts @@ -0,0 +1,76 @@ +import { Clipboard } from '@angular/cdk/clipboard' +import { DecimalPipe } from '@angular/common' +import { Component, EventEmitter, Input, Output } from '@angular/core' +import { + NgbProgressbarModule, + NgbToastModule, +} from '@ng-bootstrap/ng-bootstrap' +import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' +import { interval, take } from 'rxjs' +import { Toast } from 'src/app/services/toast.service' + +@Component({ + selector: 'pngx-toast', + imports: [ + DecimalPipe, + NgbToastModule, + NgbProgressbarModule, + NgxBootstrapIconsModule, + ], + templateUrl: './toast.component.html', + styleUrl: './toast.component.scss', +}) +export class ToastComponent { + @Input() toast: Toast + + @Input() autohide: boolean = true + + @Output() hidden: EventEmitter = new EventEmitter() + + @Output() close: EventEmitter = new EventEmitter() + + public copied: boolean = false + + constructor(private clipboard: Clipboard) {} + + onShown(toast: Toast) { + if (!this.autohide) return + + const refreshInterval = 150 + const delay = toast.delay - 500 // for fade animation + + interval(refreshInterval) + .pipe(take(Math.round(delay / refreshInterval))) + .subscribe((count) => { + toast.delayRemaining = Math.max( + 0, + delay - refreshInterval * (count + 1) + ) + }) + } + + public isDetailedError(error: any): boolean { + return ( + typeof error === 'object' && + 'status' in error && + 'statusText' in error && + 'url' in error && + 'message' in error && + 'error' in error + ) + } + + public copyError(error: any) { + this.clipboard.copy(JSON.stringify(error)) + this.copied = true + setTimeout(() => { + this.copied = false + }, 3000) + } + + getErrorText(error: any) { + let text: string = error.error?.detail ?? error.error ?? '' + if (typeof text === 'object') text = JSON.stringify(text) + return `${text.slice(0, 200)}${text.length > 200 ? '...' : ''}` + } +} diff --git a/src-ui/src/app/components/common/toasts/toasts.component.html b/src-ui/src/app/components/common/toasts/toasts.component.html index 36623161b..2178a2023 100644 --- a/src-ui/src/app/components/common/toasts/toasts.component.html +++ b/src-ui/src/app/components/common/toasts/toasts.component.html @@ -1,55 +1,3 @@ -@for (toast of toasts; track toast) { - - - {{ toast.delayRemaining / 1000 | number: '1.0-0' }} seconds -
- @if (!toast.error) { - - } - @if (toast.error) { - - } -
-

{{toast.content}}

- @if (toast.error) { -
-
- @if (isDetailedError(toast.error)) { -
-
URL
-
{{ toast.error.url }}
-
Status
-
{{ toast.error.status }} {{ toast.error.statusText }}
-
Error
-
{{ getErrorText(toast.error) }}
-
- } -
-
- -
-
-
-
- } - @if (toast.action) { -

- } -
- -
-
+@for (toast of toasts; track toast.id) { + } diff --git a/src-ui/src/app/components/common/toasts/toasts.component.scss b/src-ui/src/app/components/common/toasts/toasts.component.scss index 463f96495..e0a069dda 100644 --- a/src-ui/src/app/components/common/toasts/toasts.component.scss +++ b/src-ui/src/app/components/common/toasts/toasts.component.scss @@ -1,7 +1,7 @@ :host { position: fixed; top: 0; - right: 0; + right: calc(50% - (var(--pngx-toast-max-width) / 2)); margin: 0.3em; z-index: 1200; } @@ -9,24 +9,3 @@ .toast:not(.show) { display: block; // this corrects an ng-bootstrap bug that prevented animations } - -::ng-deep .toast-body { - position: relative; -} - -::ng-deep .toast.error { - border-color: hsla(350, 79%, 40%, 0.4); // bg-danger -} - -::ng-deep .toast.error .toast-body { - background-color: hsla(350, 79%, 40%, 0.8); // bg-danger - border-top-left-radius: inherit; - border-top-right-radius: inherit; - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; -} - -.progress { - background-color: var(--pngx-primary); - opacity: .07; -} diff --git a/src-ui/src/app/components/common/toasts/toasts.component.spec.ts b/src-ui/src/app/components/common/toasts/toasts.component.spec.ts index 449396134..bbea04c9c 100644 --- a/src-ui/src/app/components/common/toasts/toasts.component.spec.ts +++ b/src-ui/src/app/components/common/toasts/toasts.component.spec.ts @@ -1,58 +1,33 @@ -import { Clipboard } from '@angular/cdk/clipboard' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClientTesting } from '@angular/common/http/testing' -import { - ComponentFixture, - TestBed, - discardPeriodicTasks, - fakeAsync, - flush, - tick, -} from '@angular/core/testing' +import { ComponentFixture, TestBed } from '@angular/core/testing' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' -import { of } from 'rxjs' -import { ToastService } from 'src/app/services/toast.service' +import { Subject } from 'rxjs' +import { Toast, ToastService } from 'src/app/services/toast.service' import { ToastsComponent } from './toasts.component' -const toasts = [ - { - content: 'foo bar', - delay: 5000, +const toast = { + content: 'Error 2 content', + delay: 5000, + error: { + url: 'https://example.com', + status: 500, + statusText: 'Internal Server Error', + message: 'Internal server error 500 message', + error: { detail: 'Error 2 message details' }, }, - { - content: 'Error 1 content', - delay: 5000, - error: 'Error 1 string', - }, - { - content: 'Error 2 content', - delay: 5000, - error: { - url: 'https://example.com', - status: 500, - statusText: 'Internal Server Error', - message: 'Internal server error 500 message', - error: { detail: 'Error 2 message details' }, - }, - }, -] +} describe('ToastsComponent', () => { let component: ToastsComponent let fixture: ComponentFixture let toastService: ToastService - let clipboard: Clipboard + let toastSubject: Subject = new Subject() beforeEach(async () => { TestBed.configureTestingModule({ imports: [ToastsComponent, NgxBootstrapIconsModule.pick(allIcons)], providers: [ - { - provide: ToastService, - useValue: { - getToasts: () => of(toasts), - }, - }, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), ], @@ -60,95 +35,37 @@ describe('ToastsComponent', () => { fixture = TestBed.createComponent(ToastsComponent) toastService = TestBed.inject(ToastService) - clipboard = TestBed.inject(Clipboard) + jest.replaceProperty(toastService, 'showToast', toastSubject) component = fixture.componentInstance fixture.detectChanges() }) - it('should call getToasts and return toasts', fakeAsync(() => { - const spy = jest.spyOn(toastService, 'getToasts') + it('should create', () => { + expect(component).toBeTruthy() + }) - component.ngOnInit() - fixture.detectChanges() + it('should close toast', () => { + component.toasts = [toast] + const closeToastSpy = jest.spyOn(toastService, 'closeToast') + component.closeToast() + expect(component.toasts).toEqual([]) + expect(closeToastSpy).toHaveBeenCalledWith(toast) + }) - expect(spy).toHaveBeenCalled() - expect(component.toasts).toContainEqual({ - content: 'foo bar', - delay: 5000, - }) - - component.ngOnDestroy() - flush() - discardPeriodicTasks() - })) - - it('should show a toast', fakeAsync(() => { - component.ngOnInit() - fixture.detectChanges() - - expect(fixture.nativeElement.textContent).toContain('foo bar') - - component.ngOnDestroy() - flush() - discardPeriodicTasks() - })) - - it('should countdown toast', fakeAsync(() => { - component.ngOnInit() - fixture.detectChanges() - component.onShow(toasts[0]) - tick(5000) - expect(component.toasts[0].delayRemaining).toEqual(0) - component.ngOnDestroy() - flush() - discardPeriodicTasks() - })) - - it('should show an error if given with toast', fakeAsync(() => { - component.ngOnInit() - fixture.detectChanges() - - expect(fixture.nativeElement.querySelector('details')).not.toBeNull() - expect(fixture.nativeElement.textContent).toContain('Error 1 content') - - component.ngOnDestroy() - flush() - discardPeriodicTasks() - })) - - it('should show error details, support copy', fakeAsync(() => { - component.ngOnInit() - fixture.detectChanges() - - expect(fixture.nativeElement.querySelector('details')).not.toBeNull() - expect(fixture.nativeElement.textContent).toContain( - 'Error 2 message details' + it('should unsubscribe', () => { + const unsubscribeSpy = jest.spyOn( + (component as any).subscription, + 'unsubscribe' ) - - const copySpy = jest.spyOn(clipboard, 'copy') - component.copyError(toasts[2].error) - expect(copySpy).toHaveBeenCalled() - component.ngOnDestroy() - flush() - discardPeriodicTasks() - })) + expect(unsubscribeSpy).toHaveBeenCalled() + }) - it('should parse error text, add ellipsis', () => { - expect(component.getErrorText(toasts[2].error)).toEqual( - 'Error 2 message details' - ) - expect(component.getErrorText({ error: 'Error string no detail' })).toEqual( - 'Error string no detail' - ) - expect(component.getErrorText('Error string')).toEqual('') - expect( - component.getErrorText({ error: { message: 'foo error bar' } }) - ).toContain('{"message":"foo error bar"}') - expect( - component.getErrorText({ error: new Array(205).join('a') }) - ).toContain('...') + it('should subscribe to toastService', () => { + component.ngOnInit() + toastSubject.next(toast) + expect(component.toasts).toEqual([toast]) }) }) diff --git a/src-ui/src/app/components/common/toasts/toasts.component.ts b/src-ui/src/app/components/common/toasts/toasts.component.ts index bb791de11..53b6e1895 100644 --- a/src-ui/src/app/components/common/toasts/toasts.component.ts +++ b/src-ui/src/app/components/common/toasts/toasts.component.ts @@ -1,92 +1,43 @@ -import { Clipboard } from '@angular/cdk/clipboard' -import { DecimalPipe } from '@angular/common' import { Component, OnDestroy, OnInit } from '@angular/core' import { + NgbAccordionModule, NgbProgressbarModule, - NgbToastModule, } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' -import { Subscription, interval, take } from 'rxjs' +import { Subscription } from 'rxjs' import { Toast, ToastService } from 'src/app/services/toast.service' +import { ToastComponent } from '../toast/toast.component' @Component({ selector: 'pngx-toasts', templateUrl: './toasts.component.html', styleUrls: ['./toasts.component.scss'], imports: [ - DecimalPipe, - NgbToastModule, + ToastComponent, + NgbAccordionModule, NgbProgressbarModule, NgxBootstrapIconsModule, ], }) export class ToastsComponent implements OnInit, OnDestroy { - constructor( - public toastService: ToastService, - private clipboard: Clipboard - ) {} + constructor(public toastService: ToastService) {} private subscription: Subscription - public toasts: Toast[] = [] - - public copied: boolean = false - - public seconds: number = 0 + public toasts: Toast[] = [] // array to force change detection ngOnDestroy(): void { this.subscription?.unsubscribe() } ngOnInit(): void { - this.subscription = this.toastService.getToasts().subscribe((toasts) => { - this.toasts = toasts - this.toasts.forEach((t) => { - if (typeof t.error === 'string') { - try { - t.error = JSON.parse(t.error) - } catch (e) {} - } - }) + this.subscription = this.toastService.showToast.subscribe((toast) => { + this.toasts = toast ? [toast] : [] }) } - onShow(toast: Toast) { - const refreshInterval = 150 - const delay = toast.delay - 500 // for fade animation - - interval(refreshInterval) - .pipe(take(delay / refreshInterval)) - .subscribe((count) => { - toast.delayRemaining = Math.max( - 0, - delay - refreshInterval * (count + 1) - ) - }) - } - - public isDetailedError(error: any): boolean { - return ( - typeof error === 'object' && - 'status' in error && - 'statusText' in error && - 'url' in error && - 'message' in error && - 'error' in error - ) - } - - public copyError(error: any) { - this.clipboard.copy(JSON.stringify(error)) - this.copied = true - setTimeout(() => { - this.copied = false - }, 3000) - } - - getErrorText(error: any) { - let text: string = error.error?.detail ?? error.error ?? '' - if (typeof text === 'object') text = JSON.stringify(text) - return `${text.slice(0, 200)}${text.length > 200 ? '...' : ''}` + closeToast() { + this.toastService.closeToast(this.toasts[0]) + this.toasts = [] } } diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts index 5f66c68d6..621a90491 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts @@ -33,14 +33,14 @@ import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe' -import { - ConsumerStatusService, - FileStatus, -} from 'src/app/services/consumer-status.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { PermissionsService } from 'src/app/services/permissions.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { DocumentService } from 'src/app/services/rest/document.service' +import { + FileStatus, + WebsocketStatusService, +} from 'src/app/services/websocket-status.service' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { SavedViewWidgetComponent } from './saved-view-widget.component' @@ -112,7 +112,7 @@ describe('SavedViewWidgetComponent', () => { let component: SavedViewWidgetComponent let fixture: ComponentFixture let documentService: DocumentService - let consumerStatusService: ConsumerStatusService + let websocketStatusService: WebsocketStatusService let documentListViewService: DocumentListViewService let router: Router @@ -176,7 +176,7 @@ describe('SavedViewWidgetComponent', () => { }).compileComponents() documentService = TestBed.inject(DocumentService) - consumerStatusService = TestBed.inject(ConsumerStatusService) + websocketStatusService = TestBed.inject(WebsocketStatusService) documentListViewService = TestBed.inject(DocumentListViewService) router = TestBed.inject(Router) fixture = TestBed.createComponent(SavedViewWidgetComponent) @@ -235,7 +235,7 @@ describe('SavedViewWidgetComponent', () => { it('should reload on document consumption finished', () => { const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentConsumptionFinished') + .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .mockReturnValue(fileStatusSubject) const reloadSpy = jest.spyOn(component, 'reload') component.ngOnInit() diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts index 7f6c5755b..32bf7a004 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts @@ -42,7 +42,6 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe' import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe' -import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { @@ -53,6 +52,7 @@ import { import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { DocumentService } from 'src/app/services/rest/document.service' import { SettingsService } from 'src/app/services/settings.service' +import { WebsocketStatusService } from 'src/app/services/websocket-status.service' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' @Component({ @@ -94,7 +94,7 @@ export class SavedViewWidgetComponent private documentService: DocumentService, private router: Router, private list: DocumentListViewService, - private consumerStatusService: ConsumerStatusService, + private websocketStatusService: WebsocketStatusService, public openDocumentsService: OpenDocumentsService, public documentListViewService: DocumentListViewService, public permissionsService: PermissionsService, @@ -124,7 +124,7 @@ export class SavedViewWidgetComponent ngOnInit(): void { this.reload() this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE - this.consumerStatusService + this.websocketStatusService .onDocumentConsumptionFinished() .pipe(takeUntil(this.unsubscribeNotifier)) .subscribe(() => { diff --git a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.spec.ts b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.spec.ts index da0c2c083..48ca50a10 100644 --- a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.spec.ts +++ b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.spec.ts @@ -12,9 +12,9 @@ import { routes } from 'src/app/app-routing.module' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { - ConsumerStatusService, FileStatus, -} from 'src/app/services/consumer-status.service' + WebsocketStatusService, +} from 'src/app/services/websocket-status.service' import { environment } from 'src/environments/environment' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { StatisticsWidgetComponent } from './statistics-widget.component' @@ -23,7 +23,7 @@ describe('StatisticsWidgetComponent', () => { let component: StatisticsWidgetComponent let fixture: ComponentFixture let httpTestingController: HttpTestingController - let consumerStatusService: ConsumerStatusService + let websocketStatusService: WebsocketStatusService const fileStatusSubject = new Subject() beforeEach(async () => { @@ -44,9 +44,9 @@ describe('StatisticsWidgetComponent', () => { }).compileComponents() fixture = TestBed.createComponent(StatisticsWidgetComponent) - consumerStatusService = TestBed.inject(ConsumerStatusService) + websocketStatusService = TestBed.inject(WebsocketStatusService) jest - .spyOn(consumerStatusService, 'onDocumentConsumptionFinished') + .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .mockReturnValue(fileStatusSubject) component = fixture.componentInstance diff --git a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts index f54852429..0669a3666 100644 --- a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts @@ -8,8 +8,8 @@ import { first, Subject, Subscription, takeUntil } from 'rxjs' import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component' import { FILTER_HAS_TAGS_ANY } from 'src/app/data/filter-rule-type' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' -import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' +import { WebsocketStatusService } from 'src/app/services/websocket-status.service' import { environment } from 'src/environments/environment' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' @@ -51,7 +51,7 @@ export class StatisticsWidgetComponent constructor( private http: HttpClient, - private consumerStatusService: ConsumerStatusService, + private websocketConnectionService: WebsocketStatusService, private documentListViewService: DocumentListViewService ) { super() @@ -109,7 +109,7 @@ export class StatisticsWidgetComponent ngOnInit(): void { this.reload() - this.subscription = this.consumerStatusService + this.subscription = this.websocketConnectionService .onDocumentConsumptionFinished() .subscribe(() => { this.reload() diff --git a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.spec.ts b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.spec.ts index cc1591966..45ac9217a 100644 --- a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.spec.ts +++ b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.spec.ts @@ -12,13 +12,13 @@ import { NgbAlert, NgbCollapse } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { routes } from 'src/app/app-routing.module' import { PermissionsGuard } from 'src/app/guards/permissions.guard' -import { - ConsumerStatusService, - FileStatus, - FileStatusPhase, -} from 'src/app/services/consumer-status.service' import { PermissionsService } from 'src/app/services/permissions.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service' +import { + FileStatus, + FileStatusPhase, + WebsocketStatusService, +} from 'src/app/services/websocket-status.service' import { UploadFileWidgetComponent } from './upload-file-widget.component' const FAILED_STATUSES = [new FileStatus()] @@ -42,7 +42,7 @@ const DEFAULT_STATUSES = [ describe('UploadFileWidgetComponent', () => { let component: UploadFileWidgetComponent let fixture: ComponentFixture - let consumerStatusService: ConsumerStatusService + let websocketStatusService: WebsocketStatusService let uploadDocumentsService: UploadDocumentsService beforeEach(async () => { @@ -65,7 +65,7 @@ describe('UploadFileWidgetComponent', () => { ], }).compileComponents() - consumerStatusService = TestBed.inject(ConsumerStatusService) + websocketStatusService = TestBed.inject(WebsocketStatusService) uploadDocumentsService = TestBed.inject(UploadDocumentsService) fixture = TestBed.createComponent(UploadFileWidgetComponent) component = fixture.componentInstance @@ -91,14 +91,14 @@ describe('UploadFileWidgetComponent', () => { }) it('should generate stats summary', () => { - mockConsumerStatuses(consumerStatusService) + mockConsumerStatuses(websocketStatusService) expect(component.getStatusSummary()).toEqual( 'Processing: 6, Failed: 1, Added: 4' ) }) it('should report an upload progress summary', () => { - mockConsumerStatuses(consumerStatusService) + mockConsumerStatuses(websocketStatusService) expect(component.getTotalUploadProgress()).toEqual(0.75) }) @@ -117,7 +117,7 @@ describe('UploadFileWidgetComponent', () => { }) it('should enforce a maximum number of alerts', () => { - mockConsumerStatuses(consumerStatusService) + mockConsumerStatuses(websocketStatusService) fixture.detectChanges() // 5 total, 1 hidden expect(fixture.debugElement.queryAll(By.directive(NgbAlert))).toHaveLength( @@ -131,19 +131,19 @@ describe('UploadFileWidgetComponent', () => { }) it('should allow dismissing an alert', () => { - const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss') + const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss') component.dismiss(new FileStatus()) expect(dismissSpy).toHaveBeenCalled() }) it('should allow dismissing completed alerts', fakeAsync(() => { - mockConsumerStatuses(consumerStatusService) + mockConsumerStatuses(websocketStatusService) component.alertsExpanded = true fixture.detectChanges() jest .spyOn(component, 'getStatusCompleted') .mockImplementation(() => SUCCESS_STATUSES) - const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss') + const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss') component.dismissCompleted() tick(1000) fixture.detectChanges() diff --git a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts index f237ab7aa..f60cdce60 100644 --- a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts @@ -12,13 +12,13 @@ import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap' import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component' import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' -import { - ConsumerStatusService, - FileStatus, - FileStatusPhase, -} from 'src/app/services/consumer-status.service' import { SettingsService } from 'src/app/services/settings.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service' +import { + FileStatus, + FileStatusPhase, + WebsocketStatusService, +} from 'src/app/services/websocket-status.service' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' const MAX_ALERTS = 5 @@ -46,7 +46,7 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions { @ViewChildren(NgbAlert) alerts: QueryList constructor( - private consumerStatusService: ConsumerStatusService, + private websocketStatusService: WebsocketStatusService, private uploadDocumentsService: UploadDocumentsService, public settingsService: SettingsService ) { @@ -54,13 +54,13 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions { } getStatus() { - return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS) + return this.websocketStatusService.getConsumerStatus().slice(0, MAX_ALERTS) } getStatusSummary() { let strings = [] let countUploadingAndProcessing = - this.consumerStatusService.getConsumerStatusNotCompleted().length + this.websocketStatusService.getConsumerStatusNotCompleted().length let countFailed = this.getStatusFailed().length let countSuccess = this.getStatusSuccess().length if (countUploadingAndProcessing > 0) { @@ -78,27 +78,30 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions { } getStatusHidden() { - if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) + if (this.websocketStatusService.getConsumerStatus().length < MAX_ALERTS) return [] - else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS) + else + return this.websocketStatusService.getConsumerStatus().slice(MAX_ALERTS) } getStatusUploading() { - return this.consumerStatusService.getConsumerStatus( + return this.websocketStatusService.getConsumerStatus( FileStatusPhase.UPLOADING ) } getStatusFailed() { - return this.consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) + return this.websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED) } getStatusSuccess() { - return this.consumerStatusService.getConsumerStatus(FileStatusPhase.SUCCESS) + return this.websocketStatusService.getConsumerStatus( + FileStatusPhase.SUCCESS + ) } getStatusCompleted() { - return this.consumerStatusService.getConsumerStatusCompleted() + return this.websocketStatusService.getConsumerStatusCompleted() } getTotalUploadProgress() { @@ -134,12 +137,12 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions { } dismiss(status: FileStatus) { - this.consumerStatusService.dismiss(status) + this.websocketStatusService.dismiss(status) } dismissCompleted() { this.getStatusCompleted().forEach((status) => - this.consumerStatusService.dismiss(status) + this.websocketStatusService.dismiss(status) ) } diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index bc0bf41fd..a8e14c51d 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -9,9 +9,9 @@ }
- @for (setting of zoomSettings; track setting) { - } @@ -25,15 +25,20 @@
- - Download - + @if (metadata?.has_archive_version) {
- +
} @@ -351,9 +356,9 @@ -
+
@if (showThumbnailOverlay) { - Document loading... + Document loading... }
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.scss b/src-ui/src/app/components/document-detail/document-detail.component.scss index c00f7655e..3fc009020 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.scss +++ b/src-ui/src/app/components/document-detail/document-detail.component.scss @@ -85,5 +85,8 @@ textarea.rtl { > img { filter: blur(1px); + max-width: 100%; + object-fit: contain; + object-position: top; } } diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts index fb2596d4a..349e213aa 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts @@ -24,6 +24,7 @@ import { NgbModalRef, } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { DeviceDetectorService } from 'ngx-device-detector' import { of, throwError } from 'rxjs' import { routes } from 'src/app/app-routing.module' import { Correspondent } from 'src/app/data/correspondent' @@ -45,6 +46,7 @@ import { Tag } from 'src/app/data/tag' import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' +import { ComponentRouterService } from 'src/app/services/component-router.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { PermissionsService } from 'src/app/services/permissions.service' @@ -60,7 +62,10 @@ import { ToastService } from 'src/app/services/toast.service' import { environment } from 'src/environments/environment' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component' -import { DocumentDetailComponent } from './document-detail.component' +import { + DocumentDetailComponent, + ZoomSetting, +} from './document-detail.component' const doc: Document = { id: 3, @@ -126,7 +131,9 @@ describe('DocumentDetailComponent', () => { let documentListViewService: DocumentListViewService let settingsService: SettingsService let customFieldsService: CustomFieldsService + let deviceDetectorService: DeviceDetectorService let httpTestingController: HttpTestingController + let componentRouterService: ComponentRouterService let currentUserCan = true let currentUserHasObjectPermissions = true @@ -262,8 +269,10 @@ describe('DocumentDetailComponent', () => { settingsService = TestBed.inject(SettingsService) settingsService.currentUser = { id: 1 } customFieldsService = TestBed.inject(CustomFieldsService) + deviceDetectorService = TestBed.inject(DeviceDetectorService) fixture = TestBed.createComponent(DocumentDetailComponent) httpTestingController = TestBed.inject(HttpTestingController) + componentRouterService = TestBed.inject(ComponentRouterService) component = fixture.componentInstance }) @@ -448,7 +457,9 @@ describe('DocumentDetailComponent', () => { component.save(true) expect(updateSpy).toHaveBeenCalled() expect(closeSpy).toHaveBeenCalled() - expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.') + expect(toastSpy).toHaveBeenCalledWith( + 'Document "Doc 3" saved successfully.' + ) }) it('should support save without close and show success toast', () => { @@ -461,7 +472,9 @@ describe('DocumentDetailComponent', () => { component.save() expect(updateSpy).toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled() - expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.') + expect(toastSpy).toHaveBeenCalledWith( + 'Document "Doc 3" saved successfully.' + ) }) it('should show toast error on save if error occurs', () => { @@ -476,7 +489,10 @@ describe('DocumentDetailComponent', () => { component.save() expect(updateSpy).toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled() - expect(toastSpy).toHaveBeenCalledWith('Error saving document', error) + expect(toastSpy).toHaveBeenCalledWith( + 'Error saving document "Doc 3"', + error + ) }) it('should show error toast on save but close if user can no longer edit', () => { @@ -492,7 +508,9 @@ describe('DocumentDetailComponent', () => { component.save(true) expect(updateSpy).toHaveBeenCalled() expect(closeSpy).toHaveBeenCalled() - expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.') + expect(toastSpy).toHaveBeenCalledWith( + 'Document "Doc 3" saved successfully.' + ) }) it('should allow save and next', () => { @@ -568,6 +586,16 @@ describe('DocumentDetailComponent', () => { expect(navigateSpy).toHaveBeenCalledWith(['documents']) }) + it('should allow close and navigate to the last view if available', () => { + initNormally() + jest + .spyOn(componentRouterService, 'getComponentURLBefore') + .mockReturnValue('dashboard') + const navigateSpy = jest.spyOn(router, 'navigate') + component.close() + expect(navigateSpy).toHaveBeenCalledWith(['dashboard']) + }) + it('should allow close and navigate to documents by default', () => { initNormally() jest @@ -728,7 +756,7 @@ describe('DocumentDetailComponent', () => { it('should support zoom controls', () => { initNormally() - component.onZoomSelect({ target: { value: '1' } } as any) // from select + component.setZoom(ZoomSetting.One) // from select expect(component.previewZoomSetting).toEqual('1') component.increaseZoom() expect(component.previewZoomSetting).toEqual('1.5') @@ -736,18 +764,18 @@ describe('DocumentDetailComponent', () => { expect(component.previewZoomSetting).toEqual('2') component.decreaseZoom() expect(component.previewZoomSetting).toEqual('1.5') - component.onZoomSelect({ target: { value: '1' } } as any) // from select + component.setZoom(ZoomSetting.One) // from select component.decreaseZoom() expect(component.previewZoomSetting).toEqual('.75') - component.onZoomSelect({ target: { value: 'page-fit' } } as any) // from select + component.setZoom(ZoomSetting.PageFit) // from select expect(component.previewZoomScale).toEqual('page-fit') expect(component.previewZoomSetting).toEqual('1') component.increaseZoom() expect(component.previewZoomSetting).toEqual('1.5') expect(component.previewZoomScale).toEqual('page-width') - component.onZoomSelect({ target: { value: 'page-fit' } } as any) // from select + component.setZoom(ZoomSetting.PageFit) // from select expect(component.previewZoomScale).toEqual('page-fit') expect(component.previewZoomSetting).toEqual('1') component.decreaseZoom() @@ -755,6 +783,19 @@ describe('DocumentDetailComponent', () => { expect(component.previewZoomScale).toEqual('page-width') }) + it('should select correct zoom setting in dropdown', () => { + initNormally() + component.setZoom(ZoomSetting.PageFit) + expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeTruthy() + expect(component.isZoomSelected(ZoomSetting.One)).toBeFalsy() + component.setZoom(ZoomSetting.PageWidth) + expect(component.isZoomSelected(ZoomSetting.One)).toBeTruthy() + expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeFalsy() + component.setZoom(ZoomSetting.Quarter) + expect(component.isZoomSelected(ZoomSetting.Quarter)).toBeTruthy() + expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeFalsy() + }) + it('should support updating notes dynamically', () => { const notes = [ { @@ -1255,4 +1296,38 @@ describe('DocumentDetailComponent', () => { .error(new ErrorEvent('failed')) expect(component.tiffError).not.toBeUndefined() }) + + it('should support download using share sheet on mobile, direct download otherwise', () => { + const shareSpy = jest.spyOn(navigator, 'share') + const createSpy = jest.spyOn(document, 'createElement') + const urlRevokeSpy = jest.spyOn(URL, 'revokeObjectURL') + initNormally() + + // Mobile + jest.spyOn(deviceDetectorService, 'isDesktop').mockReturnValue(false) + component.download() + httpTestingController + .expectOne(`${environment.apiBaseUrl}documents/${doc.id}/download/`) + .error(new ProgressEvent('failed')) + expect(shareSpy).not.toHaveBeenCalled() + + component.download(true) + httpTestingController + .expectOne( + `${environment.apiBaseUrl}documents/${doc.id}/download/?original=true` + ) + .flush(new ArrayBuffer(100)) + expect(shareSpy).toHaveBeenCalled() + + // Desktop + shareSpy.mockClear() + jest.spyOn(deviceDetectorService, 'isDesktop').mockReturnValue(true) + component.download() + httpTestingController + .expectOne(`${environment.apiBaseUrl}documents/${doc.id}/download/`) + .flush(new ArrayBuffer(100)) + expect(shareSpy).not.toHaveBeenCalled() + expect(createSpy).toHaveBeenCalledWith('a') + expect(urlRevokeSpy).toHaveBeenCalled() + }) }) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 6b65ad335..30e34d9cf 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -20,6 +20,7 @@ import { import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' +import { DeviceDetectorService } from 'ngx-device-detector' import { BehaviorSubject, Observable, Subject } from 'rxjs' import { debounceTime, @@ -59,6 +60,7 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { FileSizePipe } from 'src/app/pipes/file-size.pipe' import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe' +import { ComponentRouterService } from 'src/app/services/component-router.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { HotKeyService } from 'src/app/services/hot-key.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service' @@ -122,7 +124,7 @@ enum ContentRenderType { TIFF = 'tiff', } -enum ZoomSetting { +export enum ZoomSetting { PageFit = 'page-fit', PageWidth = 'page-width', Quarter = '.25', @@ -194,8 +196,6 @@ export class DocumentDetailComponent previewUrl: string thumbUrl: string previewText: string - downloadUrl: string - downloadOriginalUrl: string previewLoaded: boolean = false tiffURL: string tiffError: string @@ -233,6 +233,9 @@ export class DocumentDetailComponent ogDate: Date customFields: CustomField[] + + public downloading: boolean = false + public readonly CustomFieldDataType = CustomFieldDataType public readonly ContentRenderType = ContentRenderType @@ -272,7 +275,9 @@ export class DocumentDetailComponent private userService: UserService, private customFieldsService: CustomFieldsService, private http: HttpClient, - private hotKeyService: HotKeyService + private hotKeyService: HotKeyService, + private componentRouterService: ComponentRouterService, + private deviceDetectorService: DeviceDetectorService ) { super() } @@ -323,6 +328,7 @@ export class DocumentDetailComponent } ngOnInit(): void { + this.setZoom(this.settings.get(SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING)) this.documentForm.valueChanges .pipe(takeUntil(this.unsubscribeNotifier)) .subscribe(() => { @@ -415,13 +421,6 @@ export class DocumentDetailComponent .pipe( switchMap((doc) => { this.documentId = doc.id - this.downloadUrl = this.documentsService.getDownloadUrl( - this.documentId - ) - this.downloadOriginalUrl = this.documentsService.getDownloadUrl( - this.documentId, - true - ) this.suggestions = null const openDocument = this.openDocumentService.getOpenDocument( this.documentId @@ -810,7 +809,9 @@ export class DocumentDetailComponent this.store.next(newValues) this.openDocumentService.setDirty(this.document, false) this.openDocumentService.save() - this.toastService.showInfo($localize`Document saved successfully.`) + this.toastService.showInfo( + $localize`Document "${newValues.title}" saved successfully.` + ) this.networkActive = false this.error = null if (close) { @@ -824,11 +825,16 @@ export class DocumentDetailComponent error: (error) => { this.networkActive = false if (!this.userCanEdit) { - this.toastService.showInfo($localize`Document saved successfully.`) + this.toastService.showInfo( + $localize`Document "${this.document.title}" saved successfully.` + ) close && this.close() } else { this.error = error.error - this.toastService.showError($localize`Error saving document`, error) + this.toastService.showError( + $localize`Error saving document "${this.document.title}"`, + error + ) } }, }) @@ -888,6 +894,10 @@ export class DocumentDetailComponent 'view', this.documentListViewService.activeSavedViewId, ]) + } else if (this.componentRouterService.getComponentURLBefore()) { + this.router.navigate([ + this.componentRouterService.getComponentURLBefore(), + ]) } else { this.router.navigate(['documents']) } @@ -953,7 +963,7 @@ export class DocumentDetailComponent .subscribe({ next: () => { this.toastService.showInfo( - $localize`Reprocess operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.` + $localize`Reprocess operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.` ) if (modal) { modal.close() @@ -972,6 +982,52 @@ export class DocumentDetailComponent }) } + download(original: boolean = false) { + this.downloading = true + const downloadUrl = this.documentsService.getDownloadUrl( + this.documentId, + original + ) + this.http.get(downloadUrl, { responseType: 'blob' }).subscribe({ + next: (blob) => { + this.downloading = false + const blobParts = [blob] + const file = new File( + blobParts, + original + ? this.document.original_file_name + : this.document.archived_file_name, + { + type: original ? this.document.mime_type : 'application/pdf', + } + ) + if ( + !this.deviceDetectorService.isDesktop() && + navigator.canShare && + navigator.canShare({ files: [file] }) + ) { + navigator.share({ + files: [file], + }) + } else { + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = this.document.title + a.click() + URL.revokeObjectURL(url) + } + }, + error: (error) => { + this.downloading = false + this.toastService.showError( + $localize`Error downloading document`, + error + ) + }, + }) + } + hasNext() { return this.documentListViewService.hasNext(this.documentId) } @@ -1017,14 +1073,13 @@ export class DocumentDetailComponent } } - onZoomSelect(event: Event) { - const setting = (event.target as HTMLSelectElement)?.value as ZoomSetting - if (ZoomSetting.PageFit === setting) { - this.previewZoomSetting = ZoomSetting.One + setZoom(setting: ZoomSetting) { + if (ZoomSetting.PageFit === setting || ZoomSetting.PageWidth === setting) { this.previewZoomScale = setting + this.previewZoomSetting = ZoomSetting.One } else { - this.previewZoomScale = ZoomSetting.PageWidth this.previewZoomSetting = setting + this.previewZoomScale = ZoomSetting.PageWidth } } @@ -1034,6 +1089,14 @@ export class DocumentDetailComponent ) } + isZoomSelected(setting: ZoomSetting): boolean { + if (this.previewZoomScale === ZoomSetting.PageFit) { + return setting === ZoomSetting.PageFit + } + + return this.previewZoomSetting === setting + } + getZoomSettingTitle(setting: ZoomSetting): string { switch (setting) { case ZoomSetting.PageFit: @@ -1267,7 +1330,7 @@ export class DocumentDetailComponent .subscribe({ next: () => { this.toastService.showInfo( - $localize`Split operation will begin in the background.` + $localize`Split operation for "${this.document.title}" will begin in the background.` ) modal.close() }, @@ -1306,7 +1369,7 @@ export class DocumentDetailComponent .subscribe({ next: () => { this.toastService.show({ - content: $localize`Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes.`, + content: $localize`Rotation of "${this.document.title}" will begin in the background. Close and re-open the document after the operation has completed to see the changes.`, delay: 8000, action: this.close.bind(this), actionName: $localize`Close`, @@ -1346,7 +1409,7 @@ export class DocumentDetailComponent .subscribe({ next: () => { this.toastService.showInfo( - $localize`Delete pages operation will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.` + $localize`Delete pages operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.` ) modal.close() }, diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts index 21b8f4175..aa4a07d12 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts @@ -1039,6 +1039,7 @@ describe('BulkEditorComponent', () => { httpTestingController.match( `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id` ) // listAllFilteredIds + expect(documentListViewService.selected.size).toEqual(0) }) it('should support bulk download with archive, originals or both and file formatting', () => { diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts index 5750c4b2f..9864761fa 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -268,6 +268,9 @@ export class BulkEditorComponent .pipe(first()) .subscribe({ next: () => { + if (args['delete_originals']) { + this.list.selected.clear() + } this.list.reload() this.list.reduceSelectionToFilter() this.list.selected.forEach((id) => { diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html index 6ebbd6055..84e415815 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html @@ -32,7 +32,7 @@ {{document.title | documentTitle}} } @if (displayFields.includes(DisplayField.TAGS)) { - @for (tagID of document.tags; track t) { + @for (tagID of document.tags; track tagID) { } } diff --git a/src-ui/src/app/components/document-list/document-list.component.spec.ts b/src-ui/src/app/components/document-list/document-list.component.spec.ts index 805a65846..13a938f59 100644 --- a/src-ui/src/app/components/document-list/document-list.component.spec.ts +++ b/src-ui/src/app/components/document-list/document-list.component.spec.ts @@ -38,16 +38,16 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { FilterPipe } from 'src/app/pipes/filter.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe' -import { - ConsumerStatusService, - FileStatus, -} from 'src/app/services/consumer-status.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { PermissionsService } from 'src/app/services/permissions.service' import { DocumentService } from 'src/app/services/rest/document.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' +import { + FileStatus, + WebsocketStatusService, +} from 'src/app/services/websocket-status.service' import { DocumentCardLargeComponent } from './document-card-large/document-card-large.component' import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component' import { DocumentListComponent } from './document-list.component' @@ -81,7 +81,7 @@ describe('DocumentListComponent', () => { let fixture: ComponentFixture let documentListService: DocumentListViewService let documentService: DocumentService - let consumerStatusService: ConsumerStatusService + let websocketStatusService: WebsocketStatusService let savedViewService: SavedViewService let router: Router let activatedRoute: ActivatedRoute @@ -112,7 +112,7 @@ describe('DocumentListComponent', () => { documentListService = TestBed.inject(DocumentListViewService) documentService = TestBed.inject(DocumentService) - consumerStatusService = TestBed.inject(ConsumerStatusService) + websocketStatusService = TestBed.inject(WebsocketStatusService) savedViewService = TestBed.inject(SavedViewService) router = TestBed.inject(Router) activatedRoute = TestBed.inject(ActivatedRoute) @@ -128,13 +128,24 @@ describe('DocumentListComponent', () => { const reloadSpy = jest.spyOn(documentListService, 'reload') const fileStatusSubject = new Subject() jest - .spyOn(consumerStatusService, 'onDocumentConsumptionFinished') + .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .mockReturnValue(fileStatusSubject) fixture.detectChanges() fileStatusSubject.next(new FileStatus()) expect(reloadSpy).toHaveBeenCalled() }) + it('should reload on document deleted', () => { + const reloadSpy = jest.spyOn(documentListService, 'reload') + const documentDeletedSubject = new Subject() + jest + .spyOn(websocketStatusService, 'onDocumentDeleted') + .mockReturnValue(documentDeletedSubject) + fixture.detectChanges() + documentDeletedSubject.next(true) + expect(reloadSpy).toHaveBeenCalled() + }) + it('should show score sort fields on fulltext queries', () => { documentListService.filterRules = [ { diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index b845a524a..e1f71edbc 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -43,7 +43,6 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe' import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe' -import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { HotKeyService } from 'src/app/services/hot-key.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service' @@ -51,6 +50,7 @@ import { PermissionsService } from 'src/app/services/permissions.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' +import { WebsocketStatusService } from 'src/app/services/websocket-status.service' import { filterRulesDiffer, isFullTextFilterRule, @@ -113,7 +113,7 @@ export class DocumentListComponent private router: Router, private toastService: ToastService, private modalService: NgbModal, - private consumerStatusService: ConsumerStatusService, + private websocketStatusService: WebsocketStatusService, public openDocumentsService: OpenDocumentsService, public settingsService: SettingsService, private hotKeyService: HotKeyService, @@ -234,13 +234,17 @@ export class DocumentListComponent } ngOnInit(): void { - this.consumerStatusService + this.websocketStatusService .onDocumentConsumptionFinished() .pipe(takeUntil(this.unsubscribeNotifier)) .subscribe(() => { this.list.reload() }) + this.websocketStatusService.onDocumentDeleted().subscribe(() => { + this.list.reload() + }) + this.route.paramMap .pipe( filter((params) => params.has('id')), // only on saved view e.g. /view/id diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html index 32c2a1908..fcb7bed8f 100644 --- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html +++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html @@ -94,11 +94,11 @@ { ]) })) - it('should ingest filter rules for date created after', fakeAsync(() => { - expect(component.dateCreatedAfter).toBeNull() + it('should ingest filter rules for date created after and adjust date by 1 day', fakeAsync(() => { + expect(component.dateCreatedFrom).toBeNull() component.filterRules = [ { rule_type: FILTER_CREATED_AFTER, value: '2023-05-14', }, ] - expect(component.dateCreatedAfter).toEqual('2023-05-14') + expect(component.dateCreatedFrom).toEqual('2023-05-15') })) - it('should ingest filter rules for date created before', fakeAsync(() => { - expect(component.dateCreatedBefore).toBeNull() + it('should ingest filter rules for date created from', fakeAsync(() => { + expect(component.dateCreatedFrom).toBeNull() + component.filterRules = [ + { + rule_type: FILTER_CREATED_FROM, + value: '2023-05-14', + }, + ] + expect(component.dateCreatedFrom).toEqual('2023-05-14') + })) + + it('should ingest filter rules for date created before and adjust date by 1 day', fakeAsync(() => { + expect(component.dateCreatedTo).toBeNull() component.filterRules = [ { rule_type: FILTER_CREATED_BEFORE, value: '2023-05-14', }, ] - expect(component.dateCreatedBefore).toEqual('2023-05-14') + expect(component.dateCreatedTo).toEqual('2023-05-13') })) - it('should ingest filter rules for date added after', fakeAsync(() => { - expect(component.dateAddedAfter).toBeNull() + it('should ingest filter rules for date created to', fakeAsync(() => { + expect(component.dateCreatedTo).toBeNull() + component.filterRules = [ + { + rule_type: FILTER_CREATED_TO, + value: '2023-05-14', + }, + ] + expect(component.dateCreatedTo).toEqual('2023-05-14') + })) + + it('should ingest filter rules for date added after and adjust date by 1 day', fakeAsync(() => { + expect(component.dateAddedFrom).toBeNull() component.filterRules = [ { rule_type: FILTER_ADDED_AFTER, value: '2023-05-14', }, ] - expect(component.dateAddedAfter).toEqual('2023-05-14') + expect(component.dateAddedFrom).toEqual('2023-05-15') })) - it('should ingest filter rules for date added before', fakeAsync(() => { - expect(component.dateAddedBefore).toBeNull() + it('should ingest filter rules for date added from', fakeAsync(() => { + expect(component.dateAddedFrom).toBeNull() + component.filterRules = [ + { + rule_type: FILTER_ADDED_FROM, + value: '2023-05-14', + }, + ] + expect(component.dateAddedFrom).toEqual('2023-05-14') + })) + + it('should ingest filter rules for date added before and adjust date by 1 day', fakeAsync(() => { + expect(component.dateAddedTo).toBeNull() component.filterRules = [ { rule_type: FILTER_ADDED_BEFORE, value: '2023-05-14', }, ] - expect(component.dateAddedBefore).toEqual('2023-05-14') + expect(component.dateAddedTo).toEqual('2023-05-13') + })) + + it('should ingest filter rules for date added to', fakeAsync(() => { + expect(component.dateAddedTo).toBeNull() + component.filterRules = [ + { + rule_type: FILTER_ADDED_TO, + value: '2023-05-14', + }, + ] + expect(component.dateAddedTo).toEqual('2023-05-14') })) it('should ingest filter rules for has all tags', fakeAsync(() => { @@ -1464,7 +1512,7 @@ describe('FilterEditorComponent', () => { ]) })) - it('should convert user input to correct filter rules on date created after', fakeAsync(() => { + it('should convert user input to correct filter rules on date created from', fakeAsync(() => { const dateCreatedDropdown = fixture.debugElement.queryAll( By.directive(DatesDropdownComponent) )[0] @@ -1473,18 +1521,18 @@ describe('FilterEditorComponent', () => { dateCreatedAfter.nativeElement.value = '05/14/2023' // dateCreatedAfter.triggerEventHandler('change') // TODO: why isn't ngModel triggering this on change? - component.dateCreatedAfter = '2023-05-14' + component.dateCreatedFrom = '2023-05-14' fixture.detectChanges() tick(400) expect(component.filterRules).toEqual([ { - rule_type: FILTER_CREATED_AFTER, + rule_type: FILTER_CREATED_FROM, value: '2023-05-14', }, ]) })) - it('should convert user input to correct filter rules on date created before', fakeAsync(() => { + it('should convert user input to correct filter rules on date created to', fakeAsync(() => { const dateCreatedDropdown = fixture.debugElement.queryAll( By.directive(DatesDropdownComponent) )[0] @@ -1493,12 +1541,12 @@ describe('FilterEditorComponent', () => { dateCreatedBefore.nativeElement.value = '05/14/2023' // dateCreatedBefore.triggerEventHandler('change') // TODO: why isn't ngModel triggering this on change? - component.dateCreatedBefore = '2023-05-14' + component.dateCreatedTo = '2023-05-14' fixture.detectChanges() tick(400) expect(component.filterRules).toEqual([ { - rule_type: FILTER_CREATED_BEFORE, + rule_type: FILTER_CREATED_TO, value: '2023-05-14', }, ]) @@ -1578,12 +1626,12 @@ describe('FilterEditorComponent', () => { dateAddedAfter.nativeElement.value = '05/14/2023' // dateAddedAfter.triggerEventHandler('change') // TODO: why isn't ngModel triggering this on change? - component.dateAddedAfter = '2023-05-14' + component.dateAddedFrom = '2023-05-14' fixture.detectChanges() tick(400) expect(component.filterRules).toEqual([ { - rule_type: FILTER_ADDED_AFTER, + rule_type: FILTER_ADDED_FROM, value: '2023-05-14', }, ]) @@ -1598,12 +1646,12 @@ describe('FilterEditorComponent', () => { dateAddedBefore.nativeElement.value = '05/14/2023' // dateAddedBefore.triggerEventHandler('change') // TODO: why isn't ngModel triggering this on change? - component.dateAddedBefore = '2023-05-14' + component.dateAddedTo = '2023-05-14' fixture.detectChanges() tick(400) expect(component.filterRules).toEqual([ { - rule_type: FILTER_ADDED_BEFORE, + rule_type: FILTER_ADDED_TO, value: '2023-05-14', }, ]) diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts index 6e9a3fb7d..2179efaf4 100644 --- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts +++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts @@ -38,6 +38,8 @@ import { FilterRule } from 'src/app/data/filter-rule' import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, + FILTER_ADDED_FROM, + FILTER_ADDED_TO, FILTER_ASN, FILTER_ASN_GT, FILTER_ASN_ISNULL, @@ -45,6 +47,8 @@ import { FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, + FILTER_CREATED_FROM, + FILTER_CREATED_TO, FILTER_CUSTOM_FIELDS_QUERY, FILTER_CUSTOM_FIELDS_TEXT, FILTER_DOCUMENT_TYPE, @@ -133,19 +137,19 @@ const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:\[([^\]]+)\]/g const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:\[([^\]]+)\]/g const RELATIVE_DATE_QUERYSTRINGS = [ { - relativeDate: RelativeDate.LAST_7_DAYS, + relativeDate: RelativeDate.WITHIN_1_WEEK, dateQuery: '-1 week to now', }, { - relativeDate: RelativeDate.LAST_MONTH, + relativeDate: RelativeDate.WITHIN_1_MONTH, dateQuery: '-1 month to now', }, { - relativeDate: RelativeDate.LAST_3_MONTHS, + relativeDate: RelativeDate.WITHIN_3_MONTHS, dateQuery: '-3 month to now', }, { - relativeDate: RelativeDate.LAST_YEAR, + relativeDate: RelativeDate.WITHIN_1_YEAR, dateQuery: '-1 year to now', }, ] @@ -349,10 +353,10 @@ export class FilterEditorComponent storagePathSelectionModel = new FilterableDropdownSelectionModel() customFieldQueriesModel = new CustomFieldQueriesModel() - dateCreatedBefore: string - dateCreatedAfter: string - dateAddedBefore: string - dateAddedAfter: string + dateCreatedTo: string + dateCreatedFrom: string + dateAddedTo: string + dateAddedFrom: string dateCreatedRelativeDate: RelativeDate dateAddedRelativeDate: RelativeDate @@ -385,10 +389,10 @@ export class FilterEditorComponent this.customFieldQueriesModel.clear(false) this._textFilter = null this._moreLikeId = null - this.dateAddedBefore = null - this.dateAddedAfter = null - this.dateCreatedBefore = null - this.dateCreatedAfter = null + this.dateAddedTo = null + this.dateAddedFrom = null + this.dateCreatedTo = null + this.dateCreatedFrom = null this.dateCreatedRelativeDate = null this.dateAddedRelativeDate = null this.textFilterModifier = TEXT_FILTER_MODIFIER_EQUALS @@ -458,16 +462,40 @@ export class FilterEditorComponent }) break case FILTER_CREATED_AFTER: - this.dateCreatedAfter = rule.value + // Old rules require adjusting date by a day + const createdAfter = new Date(rule.value) + createdAfter.setDate(createdAfter.getDate() + 1) + this.dateCreatedFrom = createdAfter.toISOString().split('T')[0] break case FILTER_CREATED_BEFORE: - this.dateCreatedBefore = rule.value + // Old rules require adjusting date by a day + const createdBefore = new Date(rule.value) + createdBefore.setDate(createdBefore.getDate() - 1) + this.dateCreatedTo = createdBefore.toISOString().split('T')[0] break case FILTER_ADDED_AFTER: - this.dateAddedAfter = rule.value + // Old rules require adjusting date by a day + const addedAfter = new Date(rule.value) + addedAfter.setDate(addedAfter.getDate() + 1) + this.dateAddedFrom = addedAfter.toISOString().split('T')[0] break case FILTER_ADDED_BEFORE: - this.dateAddedBefore = rule.value + // Old rules require adjusting date by a day + const addedBefore = new Date(rule.value) + addedBefore.setDate(addedBefore.getDate() - 1) + this.dateAddedTo = addedBefore.toISOString().split('T')[0] + break + case FILTER_CREATED_FROM: + this.dateCreatedFrom = rule.value + break + case FILTER_CREATED_TO: + this.dateCreatedTo = rule.value + break + case FILTER_ADDED_FROM: + this.dateAddedFrom = rule.value + break + case FILTER_ADDED_TO: + this.dateAddedTo = rule.value break case FILTER_HAS_TAGS_ALL: this.tagSelectionModel.logicalOperator = LogicalOperator.And @@ -814,28 +842,28 @@ export class FilterEditorComponent value: JSON.stringify(queries[0]), }) } - if (this.dateCreatedBefore) { + if (this.dateCreatedTo) { filterRules.push({ - rule_type: FILTER_CREATED_BEFORE, - value: this.dateCreatedBefore, + rule_type: FILTER_CREATED_TO, + value: this.dateCreatedTo, }) } - if (this.dateCreatedAfter) { + if (this.dateCreatedFrom) { filterRules.push({ - rule_type: FILTER_CREATED_AFTER, - value: this.dateCreatedAfter, + rule_type: FILTER_CREATED_FROM, + value: this.dateCreatedFrom, }) } - if (this.dateAddedBefore) { + if (this.dateAddedTo) { filterRules.push({ - rule_type: FILTER_ADDED_BEFORE, - value: this.dateAddedBefore, + rule_type: FILTER_ADDED_TO, + value: this.dateAddedTo, }) } - if (this.dateAddedAfter) { + if (this.dateAddedFrom) { filterRules.push({ - rule_type: FILTER_ADDED_AFTER, - value: this.dateAddedAfter, + rule_type: FILTER_ADDED_FROM, + value: this.dateAddedFrom, }) } if ( diff --git a/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts b/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts index 476b09106..9a17a4528 100644 --- a/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts +++ b/src-ui/src/app/components/manage/custom-fields/custom-fields.component.ts @@ -108,13 +108,16 @@ export class CustomFieldsComponent this.customFieldsService.delete(field).subscribe({ next: () => { modal.close() - this.toastService.showInfo($localize`Deleted field`) + this.toastService.showInfo($localize`Deleted field "${field.name}"`) this.customFieldsService.clearCache() this.settingsService.initializeDisplayFields() this.reload() }, error: (e) => { - this.toastService.showError($localize`Error deleting field.`, e) + this.toastService.showError( + $localize`Error deleting field "${field.name}".`, + e + ) }, }) }) diff --git a/src-ui/src/app/components/manage/mail/mail.component.spec.ts b/src-ui/src/app/components/manage/mail/mail.component.spec.ts index 3ece18061..b9f02343d 100644 --- a/src-ui/src/app/components/manage/mail/mail.component.spec.ts +++ b/src-ui/src/app/components/manage/mail/mail.component.spec.ts @@ -214,7 +214,7 @@ describe('MailComponent', () => { deleteSpy.mockReturnValueOnce(of(true)) deleteDialog.confirm() expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account') + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account "account1"') }) it('should support process mail account, show error if needed', () => { @@ -231,7 +231,9 @@ describe('MailComponent', () => { expect(toastErrorSpy).toHaveBeenCalled() processSpy.mockReturnValueOnce(of(true)) component.processAccount(mailAccounts[0] as MailAccount) - expect(toastInfoSpy).toHaveBeenCalledWith('Processing mail account') + expect(toastInfoSpy).toHaveBeenCalledWith( + 'Processing mail account "account1"' + ) }) it('should support edit / create mail rule, show error if needed', () => { @@ -274,14 +276,14 @@ describe('MailComponent', () => { const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const listAllSpy = jest.spyOn(mailRuleService, 'listAll') deleteSpy.mockReturnValueOnce( - throwError(() => new Error('error deleting mail rule')) + throwError(() => new Error('error deleting mail rule "rule1"')) ) deleteDialog.confirm() expect(toastErrorSpy).toBeCalled() deleteSpy.mockReturnValueOnce(of(true)) deleteDialog.confirm() expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule') + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule "rule1"') }) it('should support edit permissions on mail rule objects', () => { diff --git a/src-ui/src/app/components/manage/mail/mail.component.ts b/src-ui/src/app/components/manage/mail/mail.component.ts index b606a33c9..8d4222516 100644 --- a/src-ui/src/app/components/manage/mail/mail.component.ts +++ b/src-ui/src/app/components/manage/mail/mail.component.ts @@ -200,7 +200,9 @@ export class MailComponent this.mailAccountService.delete(account).subscribe({ next: () => { modal.close() - this.toastService.showInfo($localize`Deleted mail account`) + this.toastService.showInfo( + $localize`Deleted mail account "${account.name}"` + ) this.mailAccountService.clearCache() this.mailAccountService .listAll(null, null, { full_perms: true }) @@ -210,7 +212,7 @@ export class MailComponent }, error: (e) => { this.toastService.showError( - $localize`Error deleting mail account.`, + $localize`Error deleting mail account "${account.name}".`, e ) }, @@ -221,10 +223,15 @@ export class MailComponent processAccount(account: MailAccount) { this.mailAccountService.processAccount(account).subscribe({ next: () => { - this.toastService.showInfo($localize`Processing mail account`) + this.toastService.showInfo( + $localize`Processing mail account "${account.name}"` + ) }, error: (e) => { - this.toastService.showError($localize`Error processing mail account`, e) + this.toastService.showError( + $localize`Error processing mail account "${account.name}"`, + e + ) }, }) } @@ -272,7 +279,10 @@ export class MailComponent ) }, error: (e) => { - this.toastService.showError($localize`Error toggling rule.`, e) + this.toastService.showError( + $localize`Error toggling rule "${rule.name}".`, + e + ) }, }) } @@ -291,7 +301,9 @@ export class MailComponent this.mailRuleService.delete(rule).subscribe({ next: () => { modal.close() - this.toastService.showInfo($localize`Deleted mail rule`) + this.toastService.showInfo( + $localize`Deleted mail rule "${rule.name}"` + ) this.mailRuleService.clearCache() this.mailRuleService .listAll(null, null, { full_perms: true }) @@ -300,7 +312,10 @@ export class MailComponent }) }, error: (e) => { - this.toastService.showError($localize`Error deleting mail rule.`, e) + this.toastService.showError( + $localize`Error deleting mail rule "${rule.name}".`, + e + ) }, }) }) diff --git a/src-ui/src/app/components/manage/management-list/management-list.component.html b/src-ui/src/app/components/manage/management-list/management-list.component.html index ca4e93095..82fd8502c 100644 --- a/src-ui/src/app/components/manage/management-list/management-list.component.html +++ b/src-ui/src/app/components/manage/management-list/management-list.component.html @@ -73,35 +73,37 @@ } -
-
- -
- - - @if (object.document_count > 0) { - - } +
+
+
+ +
+ + + @if (object.document_count > 0) { + + } +
-
-
- - -
- @if (object.document_count > 0) { -
- +
- } + @if (object.document_count > 0) { +
+ +
+ } +
} diff --git a/src-ui/src/app/components/manage/management-list/management-list.component.ts b/src-ui/src/app/components/manage/management-list/management-list.component.ts index ecb3e2519..7f7721485 100644 --- a/src-ui/src/app/components/manage/management-list/management-list.component.ts +++ b/src-ui/src/app/components/manage/management-list/management-list.component.ts @@ -21,7 +21,6 @@ import { MATCHING_ALGORITHMS, MatchingModel, } from 'src/app/data/matching-model' -import { ObjectWithId } from 'src/app/data/object-with-id' import { ObjectWithPermissions } from 'src/app/data/object-with-permissions' import { SortableDirective, @@ -56,7 +55,7 @@ export interface ManagementListColumn { } @Directive() -export abstract class ManagementListComponent +export abstract class ManagementListComponent extends LoadingComponentWithPermissions implements OnInit, OnDestroy { @@ -195,7 +194,7 @@ export abstract class ManagementListComponent activeModal.componentInstance.succeeded.subscribe(() => { this.reloadData() this.toastService.showInfo( - $localize`Successfully updated ${this.typeName}.` + $localize`Successfully updated ${this.typeName} "${object.name}".` ) }) activeModal.componentInstance.failed.subscribe((e) => { @@ -208,7 +207,7 @@ export abstract class ManagementListComponent abstract getDeleteMessage(object: T) - filterDocuments(object: ObjectWithId) { + filterDocuments(object: MatchingModel) { this.documentListViewService.quickFilter([ { rule_type: this.filterRuleType, value: object.id.toString() }, ]) diff --git a/src-ui/src/app/components/manage/workflows/workflows.component.ts b/src-ui/src/app/components/manage/workflows/workflows.component.ts index b1f9ff6d0..edbca44c8 100644 --- a/src-ui/src/app/components/manage/workflows/workflows.component.ts +++ b/src-ui/src/app/components/manage/workflows/workflows.component.ts @@ -142,12 +142,17 @@ export class WorkflowsComponent this.workflowService.delete(workflow).subscribe({ next: () => { modal.close() - this.toastService.showInfo($localize`Deleted workflow`) + this.toastService.showInfo( + $localize`Deleted workflow "${workflow.name}".` + ) this.workflowService.clearCache() this.reload() }, error: (e) => { - this.toastService.showError($localize`Error deleting workflow.`, e) + this.toastService.showError( + $localize`Error deleting workflow "${workflow.name}".`, + e + ) }, }) }) @@ -158,14 +163,17 @@ export class WorkflowsComponent next: () => { this.toastService.showInfo( workflow.enabled - ? $localize`Enabled workflow` - : $localize`Disabled workflow` + ? $localize`Enabled workflow "${workflow.name}"` + : $localize`Disabled workflow "${workflow.name}"` ) this.workflowService.clearCache() this.reload() }, error: (e) => { - this.toastService.showError($localize`Error toggling workflow.`, e) + this.toastService.showError( + $localize`Error toggling workflow "${workflow.name}".`, + e + ) }, }) } diff --git a/src-ui/src/app/data/filter-rule-type.ts b/src-ui/src/app/data/filter-rule-type.ts index 1c6b1cdf8..dd9d8731a 100644 --- a/src-ui/src/app/data/filter-rule-type.ts +++ b/src-ui/src/app/data/filter-rule-type.ts @@ -36,6 +36,11 @@ export const FILTER_CREATED_DAY = 12 export const FILTER_ADDED_BEFORE = 13 export const FILTER_ADDED_AFTER = 14 +export const FILTER_CREATED_TO = 43 +export const FILTER_CREATED_FROM = 44 +export const FILTER_ADDED_TO = 45 +export const FILTER_ADDED_FROM = 46 + export const FILTER_MODIFIED_BEFORE = 15 export const FILTER_MODIFIED_AFTER = 16 @@ -179,6 +184,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ datatype: 'date', multi: false, }, + { + id: FILTER_CREATED_TO, + filtervar: 'created__date__lte', + datatype: 'date', + multi: false, + }, + { + id: FILTER_CREATED_FROM, + filtervar: 'created__date__gte', + datatype: 'date', + multi: false, + }, { id: FILTER_CREATED_YEAR, filtervar: 'created__year', @@ -210,6 +227,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ datatype: 'date', multi: false, }, + { + id: FILTER_ADDED_TO, + filtervar: 'added__date__lte', + datatype: 'date', + multi: false, + }, + { + id: FILTER_ADDED_FROM, + filtervar: 'added__date__gte', + datatype: 'date', + multi: false, + }, { id: FILTER_MODIFIED_BEFORE, filtervar: 'modified__date__lt', diff --git a/src-ui/src/app/data/ui-settings.ts b/src-ui/src/app/data/ui-settings.ts index b8a319d9b..c5164d6e1 100644 --- a/src-ui/src/app/data/ui-settings.ts +++ b/src-ui/src/app/data/ui-settings.ts @@ -33,6 +33,8 @@ export const SETTINGS_KEYS = { DARK_MODE_THUMB_INVERTED: 'general-settings:dark-mode:thumb-inverted', THEME_COLOR: 'general-settings:theme:color', USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer', + PDF_VIEWER_ZOOM_SETTING: + 'general-settings:document-details:pdf-viewer-zoom-setting', DATE_LOCALE: 'general-settings:date-display:date-locale', DATE_FORMAT: 'general-settings:date-display:date-format', NOTIFICATIONS_CONSUMER_NEW_DOCUMENT: @@ -269,4 +271,9 @@ export const SETTINGS: UiSetting[] = [ type: 'boolean', default: false, }, + { + key: SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING, + type: 'string', + default: 'page-width', // ZoomSetting from 'document-detail.component' + }, ] diff --git a/src-ui/src/app/data/websocket-documents-deleted-message.ts b/src-ui/src/app/data/websocket-documents-deleted-message.ts new file mode 100644 index 000000000..11ded3781 --- /dev/null +++ b/src-ui/src/app/data/websocket-documents-deleted-message.ts @@ -0,0 +1,3 @@ +export interface WebsocketDocumentsDeletedMessage { + documents: number[] +} diff --git a/src-ui/src/app/data/websocket-consumer-status-message.ts b/src-ui/src/app/data/websocket-progress-message.ts similarity index 77% rename from src-ui/src/app/data/websocket-consumer-status-message.ts rename to src-ui/src/app/data/websocket-progress-message.ts index d1ac590b1..c8e37e232 100644 --- a/src-ui/src/app/data/websocket-consumer-status-message.ts +++ b/src-ui/src/app/data/websocket-progress-message.ts @@ -1,4 +1,4 @@ -export interface WebsocketConsumerStatusMessage { +export interface WebsocketProgressMessage { filename?: string task_id?: string current_progress?: number diff --git a/src-ui/src/app/services/component-router.service.spec.ts b/src-ui/src/app/services/component-router.service.spec.ts new file mode 100644 index 000000000..b11fc8197 --- /dev/null +++ b/src-ui/src/app/services/component-router.service.spec.ts @@ -0,0 +1,102 @@ +import { TestBed } from '@angular/core/testing' +import { ActivationStart, Router } from '@angular/router' +import { Subject } from 'rxjs' +import { ComponentRouterService } from './component-router.service' + +describe('ComponentRouterService', () => { + let service: ComponentRouterService + let router: Router + let eventsSubject: Subject + + beforeEach(() => { + eventsSubject = new Subject() + TestBed.configureTestingModule({ + providers: [ + ComponentRouterService, + { + provide: Router, + useValue: { + events: eventsSubject.asObservable(), + }, + }, + ], + }) + service = TestBed.inject(ComponentRouterService) + router = TestBed.inject(Router) + }) + + it('should add to history and componentHistory on ActivationStart event', () => { + eventsSubject.next( + new ActivationStart({ + url: 'test-url', + component: { name: 'TestComponent' }, + } as any) + ) + + expect((service as any).history).toEqual(['test-url']) + expect((service as any).componentHistory).toEqual(['TestComponent']) + }) + + it('should not add duplicate component names to componentHistory', () => { + eventsSubject.next( + new ActivationStart({ + url: 'test-url-1', + component: { name: 'TestComponent' }, + } as any) + ) + eventsSubject.next( + new ActivationStart({ + url: 'test-url-2', + component: { name: 'TestComponent' }, + } as any) + ) + + expect((service as any).componentHistory.length).toBe(1) + expect((service as any).componentHistory).toEqual(['TestComponent']) + }) + + it('should return the URL of the component before the current one', () => { + eventsSubject.next( + new ActivationStart({ + url: 'test-url-1', + component: { name: 'TestComponent1' }, + } as any) + ) + eventsSubject.next( + new ActivationStart({ + url: 'test-url-2', + component: { name: 'TestComponent2' }, + } as any) + ) + + expect(service.getComponentURLBefore()).toBe('test-url-1') + }) + + it('should update the URL of the current component if the same component is loaded via a different URL', () => { + eventsSubject.next( + new ActivationStart({ + url: 'test-url-1', + component: { name: 'TestComponent' }, + } as any) + ) + eventsSubject.next( + new ActivationStart({ + url: 'test-url-2', + component: { name: 'TestComponent' }, + } as any) + ) + + expect((service as any).history).toEqual(['test-url-2']) + }) + + it('should return null if there is no previous component', () => { + eventsSubject.next( + new ActivationStart({ + url: 'test-url', + component: { name: 'TestComponent' }, + } as any) + ) + + expect(service.getComponentURLBefore()).toBeNull() + }) +}) diff --git a/src-ui/src/app/services/component-router.service.ts b/src-ui/src/app/services/component-router.service.ts new file mode 100644 index 000000000..0589ef61f --- /dev/null +++ b/src-ui/src/app/services/component-router.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core' +import { ActivationStart, Event, Router } from '@angular/router' +import { filter } from 'rxjs' + +const EXCLUDE_COMPONENTS = ['AppFrameComponent'] + +@Injectable({ + providedIn: 'root', +}) +export class ComponentRouterService { + private history: string[] = [] + private componentHistory: any[] = [] + + constructor(private router: Router) { + this.router.events + .pipe(filter((event: Event) => event instanceof ActivationStart)) + .subscribe((event: ActivationStart) => { + if ( + this.componentHistory[this.componentHistory.length - 1] !== + event.snapshot.component.name && + !EXCLUDE_COMPONENTS.includes(event.snapshot.component.name) + ) { + this.history.push(event.snapshot.url.toString()) + this.componentHistory.push(event.snapshot.component.name) + } else { + // Update the URL of the current component in case the same component was loaded via a different URL + this.history[this.history.length - 1] = event.snapshot.url.toString() + } + }) + } + + public getComponentURLBefore(): any { + if (this.componentHistory.length > 1) { + return this.history[this.history.length - 2] + } + return null + } +} diff --git a/src-ui/src/app/services/consumer-status.service.spec.ts b/src-ui/src/app/services/consumer-status.service.spec.ts deleted file mode 100644 index b699f8772..000000000 --- a/src-ui/src/app/services/consumer-status.service.spec.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { - HttpEventType, - HttpResponse, - provideHttpClient, - withInterceptorsFromDi, -} from '@angular/common/http' -import { - HttpTestingController, - provideHttpClientTesting, -} from '@angular/common/http/testing' -import { TestBed } from '@angular/core/testing' -import WS from 'jest-websocket-mock' -import { environment } from 'src/environments/environment' -import { - ConsumerStatusService, - FILE_STATUS_MESSAGES, - FileStatusPhase, -} from './consumer-status.service' -import { DocumentService } from './rest/document.service' -import { SettingsService } from './settings.service' - -describe('ConsumerStatusService', () => { - let httpTestingController: HttpTestingController - let consumerStatusService: ConsumerStatusService - let documentService: DocumentService - let settingsService: SettingsService - - const server = new WS( - `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`, - { jsonProtocol: true } - ) - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [], - providers: [ - ConsumerStatusService, - DocumentService, - SettingsService, - provideHttpClient(withInterceptorsFromDi()), - provideHttpClientTesting(), - ], - }) - - httpTestingController = TestBed.inject(HttpTestingController) - settingsService = TestBed.inject(SettingsService) - settingsService.currentUser = { - id: 1, - username: 'testuser', - is_superuser: false, - } - consumerStatusService = TestBed.inject(ConsumerStatusService) - documentService = TestBed.inject(DocumentService) - }) - - afterEach(() => { - httpTestingController.verify() - }) - - it('should update status on websocket processing progress', () => { - const task_id = '1234' - const status = consumerStatusService.newFileUpload('file.pdf') - expect(status.getProgress()).toEqual(0) - - consumerStatusService.connect() - - consumerStatusService - .onDocumentConsumptionFinished() - .subscribe((filestatus) => { - expect(filestatus.phase).toEqual(FileStatusPhase.SUCCESS) - }) - - consumerStatusService.onDocumentDetected().subscribe((filestatus) => { - expect(filestatus.phase).toEqual(FileStatusPhase.STARTED) - }) - - server.send({ - task_id, - filename: 'file.pdf', - current_progress: 50, - max_progress: 100, - document_id: 12, - status: 'WORKING', - }) - - expect(status.getProgress()).toBeCloseTo(0.6) // (0.8 * 50/100) + .2 - expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([ - status, - ]) - - server.send({ - task_id, - filename: 'file.pdf', - current_progress: 100, - max_progress: 100, - document_id: 12, - status: 'SUCCESS', - message: FILE_STATUS_MESSAGES.finished, - }) - - expect(status.getProgress()).toEqual(1) - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( - 0 - ) - expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1) - - consumerStatusService.disconnect() - }) - - it('should update status on websocket failed progress', () => { - const task_id = '1234' - const status = consumerStatusService.newFileUpload('file.pdf') - status.taskId = task_id - consumerStatusService.connect() - - consumerStatusService - .onDocumentConsumptionFailed() - .subscribe((filestatus) => { - expect(filestatus.phase).toEqual(FileStatusPhase.FAILED) - }) - - server.send({ - task_id, - filename: 'file.pdf', - current_progress: 50, - max_progress: 100, - document_id: 12, - }) - - expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([ - status, - ]) - - server.send({ - task_id, - filename: 'file.pdf', - current_progress: 50, - max_progress: 100, - document_id: 12, - status: 'FAILED', - message: FILE_STATUS_MESSAGES.document_already_exists, - }) - - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( - 0 - ) - expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1) - }) - - it('should update status on upload progress', () => { - const task_id = '1234' - const status = consumerStatusService.newFileUpload('file.pdf') - - documentService.uploadDocument({}).subscribe((event) => { - if (event.type === HttpEventType.Response) { - status.taskId = event.body['task_id'] - status.message = $localize`Upload complete, waiting...` - } else if (event.type === HttpEventType.UploadProgress) { - status.updateProgress( - FileStatusPhase.UPLOADING, - event.loaded, - event.total - ) - } - }) - - const req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/post_document/` - ) - - req.event( - new HttpResponse({ - body: { - task_id, - }, - }) - ) - - req.event({ - type: HttpEventType.UploadProgress, - loaded: 100, - total: 300, - }) - - expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) - ).toEqual([status]) - expect(consumerStatusService.getConsumerStatus()).toEqual([status]) - expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([ - status, - ]) - - req.event({ - type: HttpEventType.UploadProgress, - loaded: 300, - total: 300, - }) - - expect(status.getProgress()).toEqual(0.2) // 0.2 * 300/300 - }) - - it('should support dismiss completed', () => { - consumerStatusService.connect() - server.send({ - task_id: '1234', - filename: 'file.pdf', - current_progress: 100, - max_progress: 100, - document_id: 12, - status: 'SUCCESS', - message: 'finished', - }) - - expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1) - consumerStatusService.dismissCompleted() - expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(0) - consumerStatusService.disconnect() - }) - - it('should support dismiss', () => { - const task_id = '1234' - const status = consumerStatusService.newFileUpload('file.pdf') - status.taskId = task_id - status.updateProgress(FileStatusPhase.UPLOADING, 50, 100) - - const status2 = consumerStatusService.newFileUpload('file2.pdf') - status2.updateProgress(FileStatusPhase.UPLOADING, 50, 100) - - expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) - ).toEqual([status, status2]) - expect(consumerStatusService.getConsumerStatus()).toEqual([status, status2]) - expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([ - status, - status2, - ]) - - consumerStatusService.dismiss(status) - expect(consumerStatusService.getConsumerStatus()).toEqual([status2]) - - consumerStatusService.dismiss(status2) - expect(consumerStatusService.getConsumerStatus()).toHaveLength(0) - }) - - it('should support fail', () => { - const task_id = '1234' - const status = consumerStatusService.newFileUpload('file.pdf') - status.taskId = task_id - status.updateProgress(FileStatusPhase.UPLOADING, 50, 100) - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( - 1 - ) - expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(0) - consumerStatusService.fail(status, 'fail') - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( - 0 - ) - expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1) - }) - - it('should notify of document created on status message without upload', () => { - let detected = false - consumerStatusService.onDocumentDetected().subscribe((filestatus) => { - expect(filestatus.phase).toEqual(FileStatusPhase.STARTED) - detected = true - }) - - consumerStatusService.connect() - server.send({ - task_id: '1234', - filename: 'file.pdf', - current_progress: 0, - max_progress: 100, - message: 'new_file', - status: 'STARTED', - }) - - consumerStatusService.disconnect() - expect(detected).toBeTruthy() - }) - - it('should notify of document in progress without upload', () => { - consumerStatusService.connect() - server.send({ - task_id: '1234', - filename: 'file.pdf', - current_progress: 50, - max_progress: 100, - docuement_id: 12, - status: 'WORKING', - }) - - consumerStatusService.disconnect() - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( - 1 - ) - }) - - it('should not notify current user if document has different expected owner', () => { - consumerStatusService.connect() - server.send({ - task_id: '1234', - filename: 'file1.pdf', - current_progress: 50, - max_progress: 100, - docuement_id: 12, - owner_id: 1, - status: 'WORKING', - }) - - server.send({ - task_id: '5678', - filename: 'file2.pdf', - current_progress: 50, - max_progress: 100, - docuement_id: 13, - owner_id: 2, - status: 'WORKING', - }) - - consumerStatusService.disconnect() - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( - 1 - ) - }) -}) diff --git a/src-ui/src/app/services/toast.service.spec.ts b/src-ui/src/app/services/toast.service.spec.ts index 274ea9db6..ce50b165e 100644 --- a/src-ui/src/app/services/toast.service.spec.ts +++ b/src-ui/src/app/services/toast.service.spec.ts @@ -25,6 +25,33 @@ describe('ToastService', () => { }) }) + it('adds a unique id to toast on show', () => { + const toast = { + title: 'Title', + content: 'content', + delay: 5000, + } + toastService.show(toast) + + toastService.getToasts().subscribe((toasts) => { + expect(toasts[0].id).toBeDefined() + }) + }) + + it('parses error string to object on show', () => { + const toast = { + title: 'Title', + content: 'content', + delay: 5000, + error: 'Error string', + } + toastService.show(toast) + + toastService.getToasts().subscribe((toasts) => { + expect(toasts[0].error).toEqual('Error string') + }) + }) + it('creates toasts with defaults on showInfo and showError', () => { toastService.showInfo('Info toast') toastService.showError('Error toast') @@ -54,4 +81,29 @@ describe('ToastService', () => { expect(toasts).toHaveLength(0) }) }) + + it('clears all toasts on clearToasts', () => { + toastService.showInfo('Info toast') + toastService.showError('Error toast') + toastService.clearToasts() + + toastService.getToasts().subscribe((toasts) => { + expect(toasts).toHaveLength(0) + }) + }) + + it('suppresses popup toasts if suppressPopupToasts is true', (finish) => { + toastService.showToast.subscribe((toast) => { + expect(toast).not.toBeNull() + }) + toastService.showInfo('Info toast') + + toastService.showToast.subscribe((toast) => { + expect(toast).toBeNull() + finish() + }) + + toastService.suppressPopupToasts = true + toastService.showInfo('Info toast') + }) }) diff --git a/src-ui/src/app/services/toast.service.ts b/src-ui/src/app/services/toast.service.ts index 16c534b5c..b917bf94b 100644 --- a/src-ui/src/app/services/toast.service.ts +++ b/src-ui/src/app/services/toast.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@angular/core' import { Subject } from 'rxjs' +import { v4 as uuidv4 } from 'uuid' export interface Toast { + id?: string + content: string delay: number @@ -22,13 +25,32 @@ export interface Toast { }) export class ToastService { constructor() {} + _suppressPopupToasts: boolean + + set suppressPopupToasts(value: boolean) { + this._suppressPopupToasts = value + this.showToast.next(null) + } private toasts: Toast[] = [] private toastsSubject: Subject = new Subject() + public showToast: Subject = new Subject() + show(toast: Toast) { - this.toasts.push(toast) + if (!toast.id) { + toast.id = uuidv4() + } + if (typeof toast.error === 'string') { + try { + toast.error = JSON.parse(toast.error) + } catch (e) {} + } + this.toasts.unshift(toast) + if (!this._suppressPopupToasts) { + this.showToast.next(toast) + } this.toastsSubject.next(this.toasts) } @@ -46,7 +68,7 @@ export class ToastService { } closeToast(toast: Toast) { - let index = this.toasts.findIndex((t) => t == toast) + let index = this.toasts.findIndex((t) => t.id == toast.id) if (index > -1) { this.toasts.splice(index, 1) this.toastsSubject.next(this.toasts) @@ -56,4 +78,10 @@ export class ToastService { getToasts() { return this.toastsSubject } + + clearToasts() { + this.toasts = [] + this.toastsSubject.next(this.toasts) + this.showToast.next(null) + } } diff --git a/src-ui/src/app/services/upload-documents.service.spec.ts b/src-ui/src/app/services/upload-documents.service.spec.ts index cf0812306..28fb5b2e0 100644 --- a/src-ui/src/app/services/upload-documents.service.spec.ts +++ b/src-ui/src/app/services/upload-documents.service.spec.ts @@ -9,11 +9,11 @@ import { } from '@angular/common/http/testing' import { TestBed } from '@angular/core/testing' import { environment } from 'src/environments/environment' -import { - ConsumerStatusService, - FileStatusPhase, -} from './consumer-status.service' import { UploadDocumentsService } from './upload-documents.service' +import { + FileStatusPhase, + WebsocketStatusService, +} from './websocket-status.service' const files = [ { @@ -45,14 +45,14 @@ const fileList = { describe('UploadDocumentsService', () => { let httpTestingController: HttpTestingController let uploadDocumentsService: UploadDocumentsService - let consumerStatusService: ConsumerStatusService + let websocketStatusService: WebsocketStatusService beforeEach(() => { TestBed.configureTestingModule({ imports: [], providers: [ UploadDocumentsService, - ConsumerStatusService, + WebsocketStatusService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), ], @@ -60,7 +60,7 @@ describe('UploadDocumentsService', () => { httpTestingController = TestBed.inject(HttpTestingController) uploadDocumentsService = TestBed.inject(UploadDocumentsService) - consumerStatusService = TestBed.inject(ConsumerStatusService) + websocketStatusService = TestBed.inject(WebsocketStatusService) }) afterEach(() => { @@ -80,11 +80,11 @@ describe('UploadDocumentsService', () => { it('updates progress during upload and failure', () => { uploadDocumentsService.uploadFiles(fileList) - expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( 2 ) expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) + websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) ).toHaveLength(0) const req = httpTestingController.match( @@ -98,7 +98,7 @@ describe('UploadDocumentsService', () => { }) expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) + websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) ).toHaveLength(1) }) @@ -110,7 +110,7 @@ describe('UploadDocumentsService', () => { ) expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) + websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED) ).toHaveLength(0) req[0].flush( @@ -122,7 +122,7 @@ describe('UploadDocumentsService', () => { ) expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) + websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED) ).toHaveLength(1) uploadDocumentsService.uploadFiles(fileList) @@ -140,7 +140,7 @@ describe('UploadDocumentsService', () => { ) expect( - consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) + websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED) ).toHaveLength(2) }) diff --git a/src-ui/src/app/services/upload-documents.service.ts b/src-ui/src/app/services/upload-documents.service.ts index 8a5e42b47..602e6d8ae 100644 --- a/src-ui/src/app/services/upload-documents.service.ts +++ b/src-ui/src/app/services/upload-documents.service.ts @@ -2,11 +2,11 @@ import { HttpEventType } from '@angular/common/http' import { Injectable } from '@angular/core' import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop' import { Subscription } from 'rxjs' -import { - ConsumerStatusService, - FileStatusPhase, -} from './consumer-status.service' import { DocumentService } from './rest/document.service' +import { + FileStatusPhase, + WebsocketStatusService, +} from './websocket-status.service' @Injectable({ providedIn: 'root', @@ -16,7 +16,7 @@ export class UploadDocumentsService { constructor( private documentService: DocumentService, - private consumerStatusService: ConsumerStatusService + private websocketStatusService: WebsocketStatusService ) {} onNgxFileDrop(files: NgxFileDropEntry[]) { @@ -37,7 +37,7 @@ export class UploadDocumentsService { private uploadFile(file: File) { let formData = new FormData() formData.append('document', file, file.name) - let status = this.consumerStatusService.newFileUpload(file.name) + let status = this.websocketStatusService.newFileUpload(file.name) status.message = $localize`Connecting...` @@ -61,11 +61,11 @@ export class UploadDocumentsService { error: (error) => { switch (error.status) { case 400: { - this.consumerStatusService.fail(status, error.error.document) + this.websocketStatusService.fail(status, error.error.document) break } default: { - this.consumerStatusService.fail( + this.websocketStatusService.fail( status, $localize`HTTP error: ${error.status} ${error.statusText}` ) diff --git a/src-ui/src/app/services/websocket-status.service.spec.ts b/src-ui/src/app/services/websocket-status.service.spec.ts new file mode 100644 index 000000000..d3bf71f7e --- /dev/null +++ b/src-ui/src/app/services/websocket-status.service.spec.ts @@ -0,0 +1,375 @@ +import { + HttpEventType, + HttpResponse, + provideHttpClient, + withInterceptorsFromDi, +} from '@angular/common/http' +import { + HttpTestingController, + provideHttpClientTesting, +} from '@angular/common/http/testing' +import { TestBed } from '@angular/core/testing' +import WS from 'jest-websocket-mock' +import { environment } from 'src/environments/environment' +import { DocumentService } from './rest/document.service' +import { SettingsService } from './settings.service' +import { + FILE_STATUS_MESSAGES, + FileStatusPhase, + WebsocketStatusService, + WebsocketStatusType, +} from './websocket-status.service' + +describe('ConsumerStatusService', () => { + let httpTestingController: HttpTestingController + let websocketStatusService: WebsocketStatusService + let documentService: DocumentService + let settingsService: SettingsService + + const server = new WS( + `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`, + { jsonProtocol: true } + ) + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [ + WebsocketStatusService, + DocumentService, + SettingsService, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + ], + }) + + httpTestingController = TestBed.inject(HttpTestingController) + settingsService = TestBed.inject(SettingsService) + settingsService.currentUser = { + id: 1, + username: 'testuser', + is_superuser: false, + } + websocketStatusService = TestBed.inject(WebsocketStatusService) + documentService = TestBed.inject(DocumentService) + }) + + afterEach(() => { + httpTestingController.verify() + }) + + it('should update status on websocket processing progress', () => { + const task_id = '1234' + const status = websocketStatusService.newFileUpload('file.pdf') + expect(status.getProgress()).toEqual(0) + + websocketStatusService.connect() + + websocketStatusService + .onDocumentConsumptionFinished() + .subscribe((filestatus) => { + expect(filestatus.phase).toEqual(FileStatusPhase.SUCCESS) + }) + + websocketStatusService.onDocumentDetected().subscribe((filestatus) => { + expect(filestatus.phase).toEqual(FileStatusPhase.STARTED) + }) + + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id, + filename: 'file.pdf', + current_progress: 50, + max_progress: 100, + document_id: 12, + status: 'WORKING', + }, + }) + + expect(status.getProgress()).toBeCloseTo(0.6) // (0.8 * 50/100) + .2 + expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([ + status, + ]) + + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id, + filename: 'file.pdf', + current_progress: 100, + max_progress: 100, + document_id: 12, + status: 'SUCCESS', + message: FILE_STATUS_MESSAGES.finished, + }, + }) + + expect(status.getProgress()).toEqual(1) + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( + 0 + ) + expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1) + + websocketStatusService.disconnect() + }) + + it('should update status on websocket failed progress', () => { + const task_id = '1234' + const status = websocketStatusService.newFileUpload('file.pdf') + status.taskId = task_id + websocketStatusService.connect() + + websocketStatusService + .onDocumentConsumptionFailed() + .subscribe((filestatus) => { + expect(filestatus.phase).toEqual(FileStatusPhase.FAILED) + }) + + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id, + filename: 'file.pdf', + current_progress: 50, + max_progress: 100, + document_id: 12, + }, + }) + + expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([ + status, + ]) + + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id, + filename: 'file.pdf', + current_progress: 50, + max_progress: 100, + document_id: 12, + status: 'FAILED', + message: FILE_STATUS_MESSAGES.document_already_exists, + }, + }) + + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( + 0 + ) + expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1) + }) + + it('should update status on upload progress', () => { + const task_id = '1234' + const status = websocketStatusService.newFileUpload('file.pdf') + + documentService.uploadDocument({}).subscribe((event) => { + if (event.type === HttpEventType.Response) { + status.taskId = event.body['task_id'] + status.message = $localize`Upload complete, waiting...` + } else if (event.type === HttpEventType.UploadProgress) { + status.updateProgress( + FileStatusPhase.UPLOADING, + event.loaded, + event.total + ) + } + }) + + const req = httpTestingController.expectOne( + `${environment.apiBaseUrl}documents/post_document/` + ) + + req.event( + new HttpResponse({ + body: { + task_id, + }, + }) + ) + + req.event({ + type: HttpEventType.UploadProgress, + loaded: 100, + total: 300, + }) + + expect( + websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) + ).toEqual([status]) + expect(websocketStatusService.getConsumerStatus()).toEqual([status]) + expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([ + status, + ]) + + req.event({ + type: HttpEventType.UploadProgress, + loaded: 300, + total: 300, + }) + + expect(status.getProgress()).toEqual(0.2) // 0.2 * 300/300 + }) + + it('should support dismiss completed', () => { + websocketStatusService.connect() + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id: '1234', + filename: 'file.pdf', + current_progress: 100, + max_progress: 100, + document_id: 12, + status: 'SUCCESS', + message: 'finished', + }, + }) + + expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1) + websocketStatusService.dismissCompleted() + expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(0) + websocketStatusService.disconnect() + }) + + it('should support dismiss', () => { + const task_id = '1234' + const status = websocketStatusService.newFileUpload('file.pdf') + status.taskId = task_id + status.updateProgress(FileStatusPhase.UPLOADING, 50, 100) + + const status2 = websocketStatusService.newFileUpload('file2.pdf') + status2.updateProgress(FileStatusPhase.UPLOADING, 50, 100) + + expect( + websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) + ).toEqual([status, status2]) + expect(websocketStatusService.getConsumerStatus()).toEqual([ + status, + status2, + ]) + expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([ + status, + status2, + ]) + + websocketStatusService.dismiss(status) + expect(websocketStatusService.getConsumerStatus()).toEqual([status2]) + + websocketStatusService.dismiss(status2) + expect(websocketStatusService.getConsumerStatus()).toHaveLength(0) + }) + + it('should support fail', () => { + const task_id = '1234' + const status = websocketStatusService.newFileUpload('file.pdf') + status.taskId = task_id + status.updateProgress(FileStatusPhase.UPLOADING, 50, 100) + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( + 1 + ) + expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(0) + websocketStatusService.fail(status, 'fail') + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( + 0 + ) + expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1) + }) + + it('should notify of document created on status message without upload', () => { + let detected = false + websocketStatusService.onDocumentDetected().subscribe((filestatus) => { + expect(filestatus.phase).toEqual(FileStatusPhase.STARTED) + detected = true + }) + + websocketStatusService.connect() + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id: '1234', + filename: 'file.pdf', + current_progress: 0, + max_progress: 100, + message: 'new_file', + status: 'STARTED', + }, + }) + + websocketStatusService.disconnect() + expect(detected).toBeTruthy() + }) + + it('should notify of document in progress without upload', () => { + websocketStatusService.connect() + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id: '1234', + filename: 'file.pdf', + current_progress: 50, + max_progress: 100, + docuement_id: 12, + status: 'WORKING', + }, + }) + + websocketStatusService.disconnect() + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( + 1 + ) + }) + + it('should not notify current user if document has different expected owner', () => { + websocketStatusService.connect() + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id: '1234', + filename: 'file1.pdf', + current_progress: 50, + max_progress: 100, + docuement_id: 12, + owner_id: 1, + status: 'WORKING', + }, + }) + + server.send({ + type: WebsocketStatusType.STATUS_UPDATE, + data: { + task_id: '5678', + filename: 'file2.pdf', + current_progress: 50, + max_progress: 100, + docuement_id: 13, + owner_id: 2, + status: 'WORKING', + }, + }) + + websocketStatusService.disconnect() + expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength( + 1 + ) + }) + + it('should trigger deleted subject on document deleted', () => { + let deleted = false + websocketStatusService.onDocumentDeleted().subscribe(() => { + deleted = true + }) + + websocketStatusService.connect() + server.send({ + type: WebsocketStatusType.DOCUMENTS_DELETED, + data: { + documents: [1, 2, 3], + }, + }) + + websocketStatusService.disconnect() + expect(deleted).toBeTruthy() + }) +}) diff --git a/src-ui/src/app/services/consumer-status.service.ts b/src-ui/src/app/services/websocket-status.service.ts similarity index 71% rename from src-ui/src/app/services/consumer-status.service.ts rename to src-ui/src/app/services/websocket-status.service.ts index 40641ff81..13f82412f 100644 --- a/src-ui/src/app/services/consumer-status.service.ts +++ b/src-ui/src/app/services/websocket-status.service.ts @@ -1,9 +1,15 @@ import { Injectable } from '@angular/core' import { Subject } from 'rxjs' import { environment } from 'src/environments/environment' -import { WebsocketConsumerStatusMessage } from '../data/websocket-consumer-status-message' +import { WebsocketDocumentsDeletedMessage } from '../data/websocket-documents-deleted-message' +import { WebsocketProgressMessage } from '../data/websocket-progress-message' import { SettingsService } from './settings.service' +export enum WebsocketStatusType { + STATUS_UPDATE = 'status_update', + DOCUMENTS_DELETED = 'documents_deleted', +} + // see ProgressStatusOptions in src/documents/plugins/helpers.py export enum FileStatusPhase { STARTED = 0, @@ -85,7 +91,7 @@ export class FileStatus { @Injectable({ providedIn: 'root', }) -export class ConsumerStatusService { +export class WebsocketStatusService { constructor(private settingsService: SettingsService) {} private statusWebSocket: WebSocket @@ -95,6 +101,7 @@ export class ConsumerStatusService { private documentDetectedSubject = new Subject() private documentConsumptionFinishedSubject = new Subject() private documentConsumptionFailedSubject = new Subject() + private documentDeletedSubject = new Subject() private get(taskId: string, filename?: string) { let status = @@ -145,63 +152,75 @@ export class ConsumerStatusService { this.statusWebSocket = new WebSocket( `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/` ) - this.statusWebSocket.onmessage = (ev) => { - let statusMessage: WebsocketConsumerStatusMessage = JSON.parse(ev['data']) + this.statusWebSocket.onmessage = (ev: MessageEvent) => { + const { + type, + data: messageData, + }: { + type: WebsocketStatusType + data: WebsocketProgressMessage | WebsocketDocumentsDeletedMessage + } = JSON.parse(ev.data) - // fallback if backend didn't restrict message - if ( - statusMessage.owner_id && - statusMessage.owner_id !== this.settingsService.currentUser?.id && - !this.settingsService.currentUser?.is_superuser - ) { - return - } - - let statusMessageGet = this.get( - statusMessage.task_id, - statusMessage.filename - ) - let status = statusMessageGet.status - let created = statusMessageGet.created - - status.updateProgress( - FileStatusPhase.WORKING, - statusMessage.current_progress, - statusMessage.max_progress - ) - if ( - statusMessage.message && - statusMessage.message in FILE_STATUS_MESSAGES - ) { - status.message = FILE_STATUS_MESSAGES[statusMessage.message] - } else if (statusMessage.message) { - status.message = statusMessage.message - } - status.documentId = statusMessage.document_id - - if (statusMessage.status in FileStatusPhase) { - status.phase = FileStatusPhase[statusMessage.status] - } - - switch (status.phase) { - case FileStatusPhase.STARTED: - if (created) this.documentDetectedSubject.next(status) + switch (type) { + case WebsocketStatusType.DOCUMENTS_DELETED: + this.documentDeletedSubject.next(true) break - case FileStatusPhase.SUCCESS: - this.documentConsumptionFinishedSubject.next(status) - break - - case FileStatusPhase.FAILED: - this.documentConsumptionFailedSubject.next(status) - break - - default: + case WebsocketStatusType.STATUS_UPDATE: + this.handleProgressUpdate(messageData as WebsocketProgressMessage) break } } } + handleProgressUpdate(messageData: WebsocketProgressMessage) { + // fallback if backend didn't restrict message + if ( + messageData.owner_id && + messageData.owner_id !== this.settingsService.currentUser?.id && + !this.settingsService.currentUser?.is_superuser + ) { + return + } + + let statusMessageGet = this.get(messageData.task_id, messageData.filename) + let status = statusMessageGet.status + let created = statusMessageGet.created + + status.updateProgress( + FileStatusPhase.WORKING, + messageData.current_progress, + messageData.max_progress + ) + if (messageData.message && messageData.message in FILE_STATUS_MESSAGES) { + status.message = FILE_STATUS_MESSAGES[messageData.message] + } else if (messageData.message) { + status.message = messageData.message + } + status.documentId = messageData.document_id + + if (messageData.status in FileStatusPhase) { + status.phase = FileStatusPhase[messageData.status] + } + + switch (status.phase) { + case FileStatusPhase.STARTED: + if (created) this.documentDetectedSubject.next(status) + break + + case FileStatusPhase.SUCCESS: + this.documentConsumptionFinishedSubject.next(status) + break + + case FileStatusPhase.FAILED: + this.documentConsumptionFailedSubject.next(status) + break + + default: + break + } + } + fail(status: FileStatus, message: string) { status.message = message status.phase = FileStatusPhase.FAILED @@ -250,4 +269,8 @@ export class ConsumerStatusService { onDocumentDetected() { return this.documentDetectedSubject } + + onDocumentDeleted() { + return this.documentDeletedSubject + } } diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts index 0fbbdae0a..9db14f6c3 100644 --- a/src-ui/src/environments/environment.prod.ts +++ b/src-ui/src/environments/environment.prod.ts @@ -5,7 +5,7 @@ export const environment = { apiBaseUrl: document.baseURI + 'api/', apiVersion: '7', appTitle: 'Paperless-ngx', - version: '2.14.6', + version: '2.14.7', webSocketHost: window.location.host, webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', webSocketBaseUrl: base_url.pathname + 'ws/', diff --git a/src-ui/src/main.ts b/src-ui/src/main.ts index 83aa12dc2..484a77c82 100644 --- a/src-ui/src/main.ts +++ b/src-ui/src/main.ts @@ -34,6 +34,7 @@ import { arrowRightShort, arrowUpRight, asterisk, + bell, bodyText, boxArrowUp, boxArrowUpRight, @@ -235,6 +236,7 @@ const icons = { arrowRightShort, arrowUpRight, asterisk, + bell, braces, bodyText, boxArrowUp, diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss index 1257798b9..589356566 100644 --- a/src-ui/src/styles.scss +++ b/src-ui/src/styles.scss @@ -570,6 +570,10 @@ table.table { color: var(--bs-body-color); } +.toast { + --bs-toast-max-width: var(--pngx-toast-max-width); +} + .alert-primary { --bs-alert-color: var(--bs-primary); --bs-alert-bg: var(--pngx-primary-faded); diff --git a/src-ui/src/theme.scss b/src-ui/src/theme.scss index 9f3c9cbe9..fc8c13d3b 100644 --- a/src-ui/src/theme.scss +++ b/src-ui/src/theme.scss @@ -24,6 +24,10 @@ --pngx-bg-alt2: var(--bs-gray-200); --pngx-bg-disabled: #f7f7f7; --pngx-focus-alpha: 0.3; + --pngx-toast-max-width: 360px; + @media screen and (min-width: 1024px) { + --pngx-toast-max-width: 450px; + } } // Dark text colors allow for maintain contrast with theme color changes diff --git a/src/documents/bulk_download.py b/src/documents/bulk_download.py index 25dfb5a14..5bdc3e74a 100644 --- a/src/documents/bulk_download.py +++ b/src/documents/bulk_download.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: class BulkArchiveStrategy: - def __init__(self, zipf: ZipFile, follow_formatting: bool = False) -> None: + def __init__(self, zipf: ZipFile, *, follow_formatting: bool = False) -> None: self.zipf: ZipFile = zipf if follow_formatting: self.make_unique_filename: Callable[..., Path | str] = ( @@ -22,6 +22,7 @@ class BulkArchiveStrategy: def _filename_only( self, doc: Document, + *, archive: bool = False, folder: str = "", ) -> str: @@ -33,7 +34,10 @@ class BulkArchiveStrategy: """ counter = 0 while True: - filename: str = folder + doc.get_public_filename(archive, counter) + filename: str = folder + doc.get_public_filename( + archive=archive, + counter=counter, + ) if filename in self.zipf.namelist(): counter += 1 else: @@ -42,6 +46,7 @@ class BulkArchiveStrategy: def _formatted_filepath( self, doc: Document, + *, archive: bool = False, folder: str = "", ) -> Path: diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py index de43aed87..f6adfc8a9 100644 --- a/src/documents/bulk_edit.py +++ b/src/documents/bulk_edit.py @@ -12,6 +12,7 @@ from celery import shared_task from django.conf import settings from django.contrib.auth.models import User from django.db.models import Q +from django.utils import timezone from documents.data_models import ConsumableDocument from documents.data_models import DocumentMetadataOverrides @@ -23,6 +24,7 @@ from documents.models import Document from documents.models import DocumentType from documents.models import StoragePath from documents.permissions import set_permissions_for_object +from documents.plugins.helpers import DocumentsStatusManager from documents.tasks import bulk_update_documents from documents.tasks import consume_file from documents.tasks import update_document_content_maybe_archive_file @@ -177,6 +179,27 @@ def modify_custom_fields( field_id=field_id, defaults=defaults, ) + if custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK: + doc = Document.objects.get(id=doc_id) + reflect_doclinks(doc, custom_field, value) + + # For doc link fields that are being removed, remove symmetrical links + for doclink_being_removed_instance in CustomFieldInstance.objects.filter( + document_id__in=affected_docs, + field__id__in=remove_custom_fields, + field__data_type=CustomField.FieldDataType.DOCUMENTLINK, + value_document_ids__isnull=False, + ): + for target_doc_id in doclink_being_removed_instance.value: + remove_doclink( + document=Document.objects.get( + id=doclink_being_removed_instance.document.id, + ), + field=doclink_being_removed_instance.field, + target_doc_id=target_doc_id, + ) + + # Finally, remove the custom fields CustomFieldInstance.objects.filter( document_id__in=affected_docs, field_id__in=remove_custom_fields, @@ -197,6 +220,9 @@ def delete(doc_ids: list[int]) -> Literal["OK"]: with index.open_index_writer() as writer: for id in doc_ids: index.remove_document_by_id(writer, id) + + status_mgr = DocumentsStatusManager() + status_mgr.send_documents_deleted(doc_ids) except Exception as e: if "Data too long for column" in str(e): logger.warning( @@ -219,6 +245,7 @@ def reprocess(doc_ids: list[int]) -> Literal["OK"]: def set_permissions( doc_ids: list[int], set_permissions, + *, owner=None, merge=False, ) -> Literal["OK"]: @@ -283,6 +310,7 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]: def merge( doc_ids: list[int], + *, metadata_document_id: int | None = None, delete_originals: bool = False, user: User | None = None, @@ -361,6 +389,7 @@ def merge( def split( doc_ids: list[int], pages: list[list[int]], + *, delete_originals: bool = False, user: User | None = None, ) -> Literal["OK"]: @@ -447,3 +476,87 @@ def delete_pages(doc_ids: list[int], pages: list[int]) -> Literal["OK"]: logger.exception(f"Error deleting pages from document {doc.id}: {e}") return "OK" + + +def reflect_doclinks( + document: Document, + field: CustomField, + target_doc_ids: list[int], +): + """ + Add or remove 'symmetrical' links to `document` on all `target_doc_ids` + """ + + if target_doc_ids is None: + target_doc_ids = [] + + # Check if any documents are going to be removed from the current list of links and remove the symmetrical links + current_field_instance = CustomFieldInstance.objects.filter( + field=field, + document=document, + ).first() + if current_field_instance is not None and current_field_instance.value is not None: + for doc_id in current_field_instance.value: + if doc_id not in target_doc_ids: + remove_doclink( + document=document, + field=field, + target_doc_id=doc_id, + ) + + # Create an instance if target doc doesn't have this field or append it to an existing one + existing_custom_field_instances = { + custom_field.document_id: custom_field + for custom_field in CustomFieldInstance.objects.filter( + field=field, + document_id__in=target_doc_ids, + ) + } + custom_field_instances_to_create = [] + custom_field_instances_to_update = [] + for target_doc_id in target_doc_ids: + target_doc_field_instance = existing_custom_field_instances.get( + target_doc_id, + ) + if target_doc_field_instance is None: + custom_field_instances_to_create.append( + CustomFieldInstance( + document_id=target_doc_id, + field=field, + value_document_ids=[document.id], + ), + ) + elif target_doc_field_instance.value is None: + target_doc_field_instance.value_document_ids = [document.id] + custom_field_instances_to_update.append(target_doc_field_instance) + elif document.id not in target_doc_field_instance.value: + target_doc_field_instance.value_document_ids.append(document.id) + custom_field_instances_to_update.append(target_doc_field_instance) + + CustomFieldInstance.objects.bulk_create(custom_field_instances_to_create) + CustomFieldInstance.objects.bulk_update( + custom_field_instances_to_update, + ["value_document_ids"], + ) + Document.objects.filter(id__in=target_doc_ids).update(modified=timezone.now()) + + +def remove_doclink( + document: Document, + field: CustomField, + target_doc_id: int, +): + """ + Removes a 'symmetrical' link to `document` from the target document's existing custom field instance + """ + target_doc_field_instance = CustomFieldInstance.objects.filter( + document_id=target_doc_id, + field=field, + ).first() + if ( + target_doc_field_instance is not None + and document.id in target_doc_field_instance.value + ): + target_doc_field_instance.value.remove(document.id) + target_doc_field_instance.save() + Document.objects.filter(id=target_doc_id).update(modified=timezone.now()) diff --git a/src/documents/classifier.py b/src/documents/classifier.py index 72bf1f16c..5bc8be2c6 100644 --- a/src/documents/classifier.py +++ b/src/documents/classifier.py @@ -1,6 +1,7 @@ import logging import pickle import re +import time import warnings from collections.abc import Iterator from hashlib import sha256 @@ -141,6 +142,19 @@ class DocumentClassifier: ): raise IncompatibleClassifierVersionError("sklearn version update") + def set_last_checked(self) -> None: + # save a timestamp of the last time we checked for retraining to a file + with Path(settings.MODEL_FILE.with_suffix(".last_checked")).open("w") as f: + f.write(str(time.time())) + + def get_last_checked(self) -> float | None: + # load the timestamp of the last time we checked for retraining + try: + with Path(settings.MODEL_FILE.with_suffix(".last_checked")).open("r") as f: + return float(f.read()) + except FileNotFoundError: # pragma: no cover + return None + def save(self) -> None: target_file: Path = settings.MODEL_FILE target_file_temp: Path = target_file.with_suffix(".pickle.part") @@ -161,6 +175,7 @@ class DocumentClassifier: pickle.dump(self.storage_path_classifier, f) target_file_temp.rename(target_file) + self.set_last_checked() def train(self) -> bool: # Get non-inbox documents @@ -229,6 +244,7 @@ class DocumentClassifier: and self.last_doc_change_time >= latest_doc_change ) and self.last_auto_type_hash == hasher.digest(): logger.info("No updates since last training") + self.set_last_checked() # Set the classifier information into the cache # Caching for 50 minutes, so slightly less than the normal retrain time cache.set( diff --git a/src/documents/consumer.py b/src/documents/consumer.py index 524f5fd77..81739fa7a 100644 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -4,6 +4,7 @@ import os import tempfile from enum import Enum from pathlib import Path +from typing import TYPE_CHECKING import magic from django.conf import settings @@ -155,7 +156,11 @@ class ConsumerPlugin( """ Confirm the input file still exists where it should """ - if not os.path.isfile(self.input_doc.original_file): + if TYPE_CHECKING: + assert isinstance(self.input_doc.original_file, Path), ( + self.input_doc.original_file + ) + if not self.input_doc.original_file.is_file(): self._fail( ConsumerStatusShortMessage.FILE_NOT_FOUND, f"Cannot consume {self.input_doc.original_file}: File not found.", @@ -165,7 +170,7 @@ class ConsumerPlugin( """ Using the MD5 of the file, check this exact file doesn't already exist """ - with open(self.input_doc.original_file, "rb") as f: + with Path(self.input_doc.original_file).open("rb") as f: checksum = hashlib.md5(f.read()).hexdigest() existing_doc = Document.global_objects.filter( Q(checksum=checksum) | Q(archive_checksum=checksum), @@ -179,7 +184,7 @@ class ConsumerPlugin( log_msg += " Note: existing document is in the trash." if settings.CONSUMER_DELETE_DUPLICATES: - os.unlink(self.input_doc.original_file) + Path(self.input_doc.original_file).unlink() self._fail( msg, log_msg, @@ -238,7 +243,7 @@ class ConsumerPlugin( if not settings.PRE_CONSUME_SCRIPT: return - if not os.path.isfile(settings.PRE_CONSUME_SCRIPT): + if not Path(settings.PRE_CONSUME_SCRIPT).is_file(): self._fail( ConsumerStatusShortMessage.PRE_CONSUME_SCRIPT_NOT_FOUND, f"Configured pre-consume script " @@ -281,7 +286,7 @@ class ConsumerPlugin( if not settings.POST_CONSUME_SCRIPT: return - if not os.path.isfile(settings.POST_CONSUME_SCRIPT): + if not Path(settings.POST_CONSUME_SCRIPT).is_file(): self._fail( ConsumerStatusShortMessage.POST_CONSUME_SCRIPT_NOT_FOUND, f"Configured post-consume script " @@ -594,7 +599,7 @@ class ConsumerPlugin( document.thumbnail_path, ) - if archive_path and os.path.isfile(archive_path): + if archive_path and Path(archive_path).is_file(): document.archive_filename = generate_unique_filename( document, archive_filename=True, @@ -606,7 +611,7 @@ class ConsumerPlugin( document.archive_path, ) - with open(archive_path, "rb") as f: + with Path(archive_path).open("rb") as f: document.archive_checksum = hashlib.md5( f.read(), ).hexdigest() @@ -624,14 +629,14 @@ class ConsumerPlugin( self.unmodified_original.unlink() # https://github.com/jonaswinkler/paperless-ng/discussions/1037 - shadow_file = os.path.join( - os.path.dirname(self.input_doc.original_file), - "._" + os.path.basename(self.input_doc.original_file), + shadow_file = ( + Path(self.input_doc.original_file).parent + / f"._{Path(self.input_doc.original_file).name}" ) - if os.path.isfile(shadow_file): + if Path(shadow_file).is_file(): self.log.debug(f"Deleting file {shadow_file}") - os.unlink(shadow_file) + Path(shadow_file).unlink() except Exception as e: self._fail( @@ -716,7 +721,7 @@ class ConsumerPlugin( create_date = date self.log.debug(f"Creation date from parse_date: {create_date}") else: - stats = os.stat(self.input_doc.original_file) + stats = Path(self.input_doc.original_file).stat() create_date = timezone.make_aware( datetime.datetime.fromtimestamp(stats.st_mtime), ) @@ -812,7 +817,10 @@ class ConsumerPlugin( ) # adds to document def _write(self, storage_type, source, target): - with open(source, "rb") as read_file, open(target, "wb") as write_file: + with ( + Path(source).open("rb") as read_file, + Path(target).open("wb") as write_file, + ): write_file.write(read_file.read()) # Attempt to copy file's original stats, but it's ok if we can't diff --git a/src/documents/file_handling.py b/src/documents/file_handling.py index 4198ecabb..3d1a643df 100644 --- a/src/documents/file_handling.py +++ b/src/documents/file_handling.py @@ -43,7 +43,7 @@ def delete_empty_directories(directory, root): directory = os.path.normpath(os.path.dirname(directory)) -def generate_unique_filename(doc, archive_filename=False): +def generate_unique_filename(doc, *, archive_filename=False): """ Generates a unique filename for doc in settings.ORIGINALS_DIR. @@ -77,7 +77,7 @@ def generate_unique_filename(doc, archive_filename=False): while True: new_filename = generate_filename( doc, - counter, + counter=counter, archive_filename=archive_filename, ) if new_filename == old_filename: @@ -92,6 +92,7 @@ def generate_unique_filename(doc, archive_filename=False): def generate_filename( doc: Document, + *, counter=0, append_gpg=True, archive_filename=False, diff --git a/src/documents/filters.py b/src/documents/filters.py index 185ba7b6f..fab029312 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -41,7 +41,19 @@ from documents.models import Tag CHAR_KWARGS = ["istartswith", "iendswith", "icontains", "iexact"] ID_KWARGS = ["in", "exact"] INT_KWARGS = ["exact", "gt", "gte", "lt", "lte", "isnull"] -DATE_KWARGS = ["year", "month", "day", "date__gt", "gt", "date__lt", "lt"] +DATE_KWARGS = [ + "year", + "month", + "day", + "date__gt", + "date__gte", + "gt", + "gte", + "date__lt", + "date__lte", + "lt", + "lte", +] CUSTOM_FIELD_QUERY_MAX_DEPTH = 10 CUSTOM_FIELD_QUERY_MAX_ATOMS = 20 @@ -85,7 +97,7 @@ class StoragePathFilterSet(FilterSet): class ObjectFilter(Filter): - def __init__(self, exclude=False, in_list=False, field_name=""): + def __init__(self, *, exclude=False, in_list=False, field_name=""): super().__init__() self.exclude = exclude self.in_list = in_list diff --git a/src/documents/index.py b/src/documents/index.py index 4c5afb505..4b11325ff 100644 --- a/src/documents/index.py +++ b/src/documents/index.py @@ -85,7 +85,7 @@ def get_schema() -> Schema: ) -def open_index(recreate=False) -> FileIndex: +def open_index(*, recreate=False) -> FileIndex: try: if exists_in(settings.INDEX_DIR) and not recreate: return open_dir(settings.INDEX_DIR, schema=get_schema()) @@ -101,7 +101,7 @@ def open_index(recreate=False) -> FileIndex: @contextmanager -def open_index_writer(optimize=False) -> AsyncWriter: +def open_index_writer(*, optimize=False) -> AsyncWriter: writer = AsyncWriter(open_index()) try: @@ -425,7 +425,7 @@ def autocomplete( def get_permissions_criterias(user: User | None = None) -> list: - user_criterias = [query.Term("has_owner", False)] + user_criterias = [query.Term("has_owner", text=False)] if user is not None: if user.is_superuser: # superusers see all docs user_criterias = [] diff --git a/src/documents/management/commands/convert_mariadb_uuid.py b/src/documents/management/commands/convert_mariadb_uuid.py index 4000e67cb..76ccf9e76 100644 --- a/src/documents/management/commands/convert_mariadb_uuid.py +++ b/src/documents/management/commands/convert_mariadb_uuid.py @@ -9,7 +9,7 @@ class Command(BaseCommand): # This code is taken almost entirely from https://github.com/wagtail/wagtail/pull/11912 with all credit to the original author. help = "Converts UUID columns from char type to the native UUID type used in MariaDB 10.7+ and Django 5.0+." - def convert_field(self, model, field_name, null=False): + def convert_field(self, model, field_name, *, null=False): if model._meta.get_field(field_name).model != model: # pragma: no cover # Field is inherited from a parent model return diff --git a/src/documents/management/commands/document_consumer.py b/src/documents/management/commands/document_consumer.py index 6b2706733..36dcc7706 100644 --- a/src/documents/management/commands/document_consumer.py +++ b/src/documents/management/commands/document_consumer.py @@ -248,15 +248,15 @@ class Command(BaseCommand): return if settings.CONSUMER_POLLING == 0 and INotify: - self.handle_inotify(directory, recursive, options["testing"]) + self.handle_inotify(directory, recursive, is_testing=options["testing"]) else: if INotify is None and settings.CONSUMER_POLLING == 0: # pragma: no cover logger.warning("Using polling as INotify import failed") - self.handle_polling(directory, recursive, options["testing"]) + self.handle_polling(directory, recursive, is_testing=options["testing"]) logger.debug("Consumer exiting.") - def handle_polling(self, directory, recursive, is_testing: bool): + def handle_polling(self, directory, recursive, *, is_testing: bool): logger.info(f"Polling directory for changes: {directory}") timeout = None @@ -283,7 +283,7 @@ class Command(BaseCommand): observer.stop() observer.join() - def handle_inotify(self, directory, recursive, is_testing: bool): + def handle_inotify(self, directory, recursive, *, is_testing: bool): logger.info(f"Using inotify to watch directory for changes: {directory}") timeout_ms = None diff --git a/src/documents/migrations/1012_fix_archive_files.py b/src/documents/migrations/1012_fix_archive_files.py index 1d12c439b..46951471e 100644 --- a/src/documents/migrations/1012_fix_archive_files.py +++ b/src/documents/migrations/1012_fix_archive_files.py @@ -84,7 +84,7 @@ def source_path(doc): return os.path.join(settings.ORIGINALS_DIR, fname) -def generate_unique_filename(doc, archive_filename=False): +def generate_unique_filename(doc, *, archive_filename=False): if archive_filename: old_filename = doc.archive_filename root = settings.ARCHIVE_DIR @@ -97,7 +97,7 @@ def generate_unique_filename(doc, archive_filename=False): while True: new_filename = generate_filename( doc, - counter, + counter=counter, archive_filename=archive_filename, ) if new_filename == old_filename: @@ -110,7 +110,7 @@ def generate_unique_filename(doc, archive_filename=False): return new_filename -def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False): +def generate_filename(doc, *, counter=0, append_gpg=True, archive_filename=False): path = "" try: diff --git a/src/documents/migrations/1062_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1062_alter_savedviewfilterrule_rule_type.py new file mode 100644 index 000000000..0b0e3cba3 --- /dev/null +++ b/src/documents/migrations/1062_alter_savedviewfilterrule_rule_type.py @@ -0,0 +1,69 @@ +# Generated by Django 5.1.4 on 2025-02-06 05:54 + +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + dependencies = [ + ("documents", "1061_workflowactionwebhook_as_json"), + ] + + operations = [ + migrations.AlterField( + model_name="savedviewfilterrule", + name="rule_type", + field=models.PositiveIntegerField( + choices=[ + (0, "title contains"), + (1, "content contains"), + (2, "ASN is"), + (3, "correspondent is"), + (4, "document type is"), + (5, "is in inbox"), + (6, "has tag"), + (7, "has any tag"), + (8, "created before"), + (9, "created after"), + (10, "created year is"), + (11, "created month is"), + (12, "created day is"), + (13, "added before"), + (14, "added after"), + (15, "modified before"), + (16, "modified after"), + (17, "does not have tag"), + (18, "does not have ASN"), + (19, "title or content contains"), + (20, "fulltext query"), + (21, "more like this"), + (22, "has tags in"), + (23, "ASN greater than"), + (24, "ASN less than"), + (25, "storage path is"), + (26, "has correspondent in"), + (27, "does not have correspondent in"), + (28, "has document type in"), + (29, "does not have document type in"), + (30, "has storage path in"), + (31, "does not have storage path in"), + (32, "owner is"), + (33, "has owner in"), + (34, "does not have owner"), + (35, "does not have owner in"), + (36, "has custom field value"), + (37, "is shared by me"), + (38, "has custom fields"), + (39, "has custom field in"), + (40, "does not have custom field in"), + (41, "does not have custom field"), + (42, "custom fields query"), + (43, "created to"), + (44, "created from"), + (45, "added to"), + (46, "added from"), + ], + verbose_name="rule type", + ), + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 79856b837..25e3c62fd 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -337,7 +337,7 @@ class Document(SoftDeleteModel, ModelWithOwner): def archive_file(self): return open(self.archive_path, "rb") - def get_public_filename(self, archive=False, counter=0, suffix=None) -> str: + def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str: """ Returns a sanitized filename for the document, not including any paths. """ @@ -522,6 +522,10 @@ class SavedViewFilterRule(models.Model): (40, _("does not have custom field in")), (41, _("does not have custom field")), (42, _("custom fields query")), + (43, _("created to")), + (44, _("created from")), + (45, _("added to")), + (46, _("added from")), ] saved_view = models.ForeignKey( diff --git a/src/documents/parsers.py b/src/documents/parsers.py index 2d73dc63f..28d903fdd 100644 --- a/src/documents/parsers.py +++ b/src/documents/parsers.py @@ -41,7 +41,7 @@ DATE_REGEX = re.compile( r"(\b|(?!=([_-])))(\d{1,2}[\. ]+[a-zéûäëčžúřěáíóńźçŞğü]{3,9} \d{4}|[a-zéûäëčžúřěáíóńźçŞğü]{3,9} \d{1,2}, \d{4})(\b|(?=([_-])))|" r"(\b|(?!=([_-])))([^\W\d_]{3,9} \d{1,2}, (\d{4}))(\b|(?=([_-])))|" r"(\b|(?!=([_-])))([^\W\d_]{3,9} \d{4})(\b|(?=([_-])))|" - r"(\b|(?!=([_-])))(\d{1,2}[^ ]{2}[\. ]+[^ ]{3,9}[ \.\/-]\d{4})(\b|(?=([_-])))|" + r"(\b|(?!=([_-])))(\d{1,2}[^ 0-9]{2}[\. ]+[^ ]{3,9}[ \.\/-]\d{4})(\b|(?=([_-])))|" r"(\b|(?!=([_-])))(\b\d{1,2}[ \.\/-][a-zéûäëčžúřěáíóńźçŞğü]{3}[ \.\/-]\d{4})(\b|(?=([_-])))", re.IGNORECASE, ) @@ -133,6 +133,7 @@ def get_parser_class_for_mime_type(mime_type: str) -> type["DocumentParser"] | N def run_convert( input_file, output_file, + *, density=None, scale=None, alpha=None, diff --git a/src/documents/permissions.py b/src/documents/permissions.py index 464916ad4..4380c6994 100644 --- a/src/documents/permissions.py +++ b/src/documents/permissions.py @@ -58,7 +58,7 @@ def get_groups_with_only_permission(obj, codename): return Group.objects.filter(id__in=group_object_perm_group_ids).distinct() -def set_permissions_for_object(permissions: list[str], object, merge: bool = False): +def set_permissions_for_object(permissions: list[str], object, *, merge: bool = False): """ Set permissions for an object. The permissions are given as a list of strings in the format "action_modelname", e.g. "view_document". diff --git a/src/documents/plugins/helpers.py b/src/documents/plugins/helpers.py index 20380b852..3315ec60e 100644 --- a/src/documents/plugins/helpers.py +++ b/src/documents/plugins/helpers.py @@ -15,16 +15,14 @@ class ProgressStatusOptions(str, enum.Enum): FAILED = "FAILED" -class ProgressManager: +class BaseStatusManager: """ Handles sending of progress information via the channel layer, with proper management of the open/close of the layer to ensure messages go out and everything is cleaned up """ - def __init__(self, filename: str, task_id: str | None = None) -> None: - self.filename = filename + def __init__(self) -> None: self._channel: RedisPubSubChannelLayer | None = None - self.task_id = task_id def __enter__(self): self.open() @@ -49,6 +47,24 @@ class ProgressManager: async_to_sync(self._channel.flush) self._channel = None + def send(self, payload: dict[str, str | int | None]) -> None: + # Ensure the layer is open + self.open() + + # Just for IDEs + if TYPE_CHECKING: + assert self._channel is not None + + # Construct and send the update + async_to_sync(self._channel.group_send)("status_updates", payload) + + +class ProgressManager(BaseStatusManager): + def __init__(self, filename: str | None = None, task_id: str | None = None) -> None: + super().__init__() + self.filename = filename + self.task_id = task_id + def send_progress( self, status: ProgressStatusOptions, @@ -57,13 +73,6 @@ class ProgressManager: max_progress: int, extra_args: dict[str, str | int | None] | None = None, ) -> None: - # Ensure the layer is open - self.open() - - # Just for IDEs - if TYPE_CHECKING: - assert self._channel is not None - payload = { "type": "status_update", "data": { @@ -78,5 +87,16 @@ class ProgressManager: if extra_args is not None: payload["data"].update(extra_args) - # Construct and send the update - async_to_sync(self._channel.group_send)("status_updates", payload) + self.send(payload) + + +class DocumentsStatusManager(BaseStatusManager): + def send_documents_deleted(self, documents: list[int]) -> None: + payload = { + "type": "documents_deleted", + "data": { + "documents": documents, + }, + } + + self.send(payload) diff --git a/src/documents/sanity_checker.py b/src/documents/sanity_checker.py index 9d44ff345..28d2024e7 100644 --- a/src/documents/sanity_checker.py +++ b/src/documents/sanity_checker.py @@ -57,7 +57,7 @@ class SanityCheckFailedException(Exception): pass -def check_sanity(progress=False) -> SanityCheckMessages: +def check_sanity(*, progress=False) -> SanityCheckMessages: messages = SanityCheckMessages() present_files = { diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 0732fd242..4adadbcb2 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -16,7 +16,6 @@ from django.core.validators import DecimalValidator from django.core.validators import MaxLengthValidator from django.core.validators import RegexValidator from django.core.validators import integer_validator -from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.text import slugify from django.utils.translation import gettext as _ @@ -647,7 +646,7 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer): if custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK: # prior to update so we can look for any docs that are going to be removed - self.reflect_doclinks(document, custom_field, validated_data["value"]) + bulk_edit.reflect_doclinks(document, custom_field, validated_data["value"]) # Actually update or create the instance, providing the value # to fill in the correct attribute based on the type @@ -767,89 +766,6 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer): return ret - def reflect_doclinks( - self, - document: Document, - field: CustomField, - target_doc_ids: list[int], - ): - """ - Add or remove 'symmetrical' links to `document` on all `target_doc_ids` - """ - - if target_doc_ids is None: - target_doc_ids = [] - - # Check if any documents are going to be removed from the current list of links and remove the symmetrical links - current_field_instance = CustomFieldInstance.objects.filter( - field=field, - document=document, - ).first() - if ( - current_field_instance is not None - and current_field_instance.value is not None - ): - for doc_id in current_field_instance.value: - if doc_id not in target_doc_ids: - self.remove_doclink(document, field, doc_id) - - # Create an instance if target doc doesn't have this field or append it to an existing one - existing_custom_field_instances = { - custom_field.document_id: custom_field - for custom_field in CustomFieldInstance.objects.filter( - field=field, - document_id__in=target_doc_ids, - ) - } - custom_field_instances_to_create = [] - custom_field_instances_to_update = [] - for target_doc_id in target_doc_ids: - target_doc_field_instance = existing_custom_field_instances.get( - target_doc_id, - ) - if target_doc_field_instance is None: - custom_field_instances_to_create.append( - CustomFieldInstance( - document_id=target_doc_id, - field=field, - value_document_ids=[document.id], - ), - ) - elif target_doc_field_instance.value is None: - target_doc_field_instance.value_document_ids = [document.id] - custom_field_instances_to_update.append(target_doc_field_instance) - elif document.id not in target_doc_field_instance.value: - target_doc_field_instance.value_document_ids.append(document.id) - custom_field_instances_to_update.append(target_doc_field_instance) - - CustomFieldInstance.objects.bulk_create(custom_field_instances_to_create) - CustomFieldInstance.objects.bulk_update( - custom_field_instances_to_update, - ["value_document_ids"], - ) - Document.objects.filter(id__in=target_doc_ids).update(modified=timezone.now()) - - @staticmethod - def remove_doclink( - document: Document, - field: CustomField, - target_doc_id: int, - ): - """ - Removes a 'symmetrical' link to `document` from the target document's existing custom field instance - """ - target_doc_field_instance = CustomFieldInstance.objects.filter( - document_id=target_doc_id, - field=field, - ).first() - if ( - target_doc_field_instance is not None - and document.id in target_doc_field_instance.value - ): - target_doc_field_instance.value.remove(document.id) - target_doc_field_instance.save() - Document.objects.filter(id=target_doc_id).update(modified=timezone.now()) - class Meta: model = CustomFieldInstance fields = [ @@ -951,7 +867,7 @@ class DocumentSerializer( ): # Doc link field is being removed entirely for doc_id in custom_field_instance.value: - CustomFieldInstanceSerializer.remove_doclink( + bulk_edit.remove_doclink( instance, custom_field_instance.field, doc_id, diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index e60585a37..1c4d36694 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -85,6 +85,7 @@ def _suggestion_printer( def set_correspondent( sender, document: Document, + *, logging_group=None, classifier: DocumentClassifier | None = None, replace=False, @@ -140,6 +141,7 @@ def set_correspondent( def set_document_type( sender, document: Document, + *, logging_group=None, classifier: DocumentClassifier | None = None, replace=False, @@ -196,6 +198,7 @@ def set_document_type( def set_tags( sender, document: Document, + *, logging_group=None, classifier: DocumentClassifier | None = None, replace=False, @@ -251,6 +254,7 @@ def set_tags( def set_storage_path( sender, document: Document, + *, logging_group=None, classifier: DocumentClassifier | None = None, replace=False, @@ -353,7 +357,7 @@ def cleanup_document_deletion(sender, instance, **kwargs): f"{filename} could not be deleted: {e}", ) elif filename and not os.path.isfile(filename): - logger.warn(f"Expected {filename} tp exist, but it did not") + logger.warning(f"Expected {filename} to exist, but it did not") delete_empty_directories( os.path.dirname(instance.source_path), diff --git a/src/documents/tasks.py b/src/documents/tasks.py index 8b0cbf249..d8539d1ab 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -63,7 +63,7 @@ def index_optimize(): writer.commit(optimize=True) -def index_reindex(progress_bar_disable=False): +def index_reindex(*, progress_bar_disable=False): documents = Document.objects.all() ix = index.open_index(recreate=True) diff --git a/src/documents/tests/test_api_bulk_download.py b/src/documents/tests/test_api_bulk_download.py index 31bf182b5..a7e8f5df3 100644 --- a/src/documents/tests/test_api_bulk_download.py +++ b/src/documents/tests/test_api_bulk_download.py @@ -1,7 +1,6 @@ import datetime import io import json -import os import shutil import zipfile @@ -15,9 +14,10 @@ from documents.models import Correspondent from documents.models import Document from documents.models import DocumentType from documents.tests.utils import DirectoriesMixin +from documents.tests.utils import SampleDirMixin -class TestBulkDownload(DirectoriesMixin, APITestCase): +class TestBulkDownload(DirectoriesMixin, SampleDirMixin, APITestCase): ENDPOINT = "/api/documents/bulk_download/" def setUp(self): @@ -51,22 +51,10 @@ class TestBulkDownload(DirectoriesMixin, APITestCase): archive_checksum="D", ) - shutil.copy( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - self.doc2.source_path, - ) - shutil.copy( - os.path.join(os.path.dirname(__file__), "samples", "simple.png"), - self.doc2b.source_path, - ) - shutil.copy( - os.path.join(os.path.dirname(__file__), "samples", "simple.jpg"), - self.doc3.source_path, - ) - shutil.copy( - os.path.join(os.path.dirname(__file__), "samples", "test_with_bom.pdf"), - self.doc3.archive_path, - ) + shutil.copy(self.SAMPLE_DIR / "simple.pdf", self.doc2.source_path) + shutil.copy(self.SAMPLE_DIR / "simple.png", self.doc2b.source_path) + shutil.copy(self.SAMPLE_DIR / "simple.jpg", self.doc3.source_path) + shutil.copy(self.SAMPLE_DIR / "test_with_bom.pdf", self.doc3.archive_path) def test_download_originals(self): response = self.client.post( diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py index 70db15217..b7a4f4e2f 100644 --- a/src/documents/tests/test_api_documents.py +++ b/src/documents/tests/test_api_documents.py @@ -1,5 +1,4 @@ import datetime -import os import shutil import tempfile import uuid @@ -8,6 +7,7 @@ from binascii import hexlify from datetime import date from datetime import timedelta from pathlib import Path +from typing import TYPE_CHECKING from unittest import mock import celery @@ -171,19 +171,18 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): content = b"This is a test" content_thumbnail = b"thumbnail content" - with open(filename, "wb") as f: + with Path(filename).open("wb") as f: f.write(content) doc = Document.objects.create( title="none", - filename=os.path.basename(filename), + filename=Path(filename).name, mime_type="application/pdf", ) - with open( - os.path.join(self.dirs.thumbnail_dir, f"{doc.pk:07d}.webp"), - "wb", - ) as f: + if TYPE_CHECKING: + assert isinstance(self.dirs.thumbnail_dir, Path), self.dirs.thumbnail_dir + with (self.dirs.thumbnail_dir / f"{doc.pk:07d}.webp").open("wb") as f: f.write(content_thumbnail) response = self.client.get(f"/api/documents/{doc.pk}/download/") @@ -217,7 +216,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): content = b"This is a test" content_thumbnail = b"thumbnail content" - with open(filename, "wb") as f: + with Path(filename).open("wb") as f: f.write(content) user1 = User.objects.create_user(username="test1") @@ -229,15 +228,12 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): doc = Document.objects.create( title="none", - filename=os.path.basename(filename), + filename=Path(filename).name, mime_type="application/pdf", owner=user1, ) - with open( - os.path.join(self.dirs.thumbnail_dir, f"{doc.pk:07d}.webp"), - "wb", - ) as f: + with (Path(self.dirs.thumbnail_dir) / f"{doc.pk:07d}.webp").open("wb") as f: f.write(content_thumbnail) response = self.client.get(f"/api/documents/{doc.pk}/download/") @@ -272,10 +268,10 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): mime_type="application/pdf", ) - with open(doc.source_path, "wb") as f: + with Path(doc.source_path).open("wb") as f: f.write(content) - with open(doc.archive_path, "wb") as f: + with Path(doc.archive_path).open("wb") as f: f.write(content_archive) response = self.client.get(f"/api/documents/{doc.pk}/download/") @@ -305,7 +301,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): def test_document_actions_not_existing_file(self): doc = Document.objects.create( title="none", - filename=os.path.basename("asd"), + filename=Path("asd").name, mime_type="application/pdf", ) @@ -1026,10 +1022,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f}, @@ -1061,10 +1054,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", { @@ -1095,10 +1085,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"documenst": f}, @@ -1111,10 +1098,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.zip"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.zip").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f}, @@ -1127,10 +1111,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "title": "my custom title"}, @@ -1152,10 +1133,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): ) c = Correspondent.objects.create(name="test-corres") - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "correspondent": c.id}, @@ -1176,10 +1154,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "correspondent": 3456}, @@ -1194,10 +1169,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): ) dt = DocumentType.objects.create(name="invoice") - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "document_type": dt.id}, @@ -1218,10 +1190,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "document_type": 34578}, @@ -1236,10 +1205,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): ) sp = StoragePath.objects.create(name="invoices") - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "storage_path": sp.id}, @@ -1260,10 +1226,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "storage_path": 34578}, @@ -1279,10 +1242,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): t1 = Tag.objects.create(name="tag1") t2 = Tag.objects.create(name="tag2") - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "tags": [t2.id, t1.id]}, @@ -1305,10 +1265,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): t1 = Tag.objects.create(name="tag1") t2 = Tag.objects.create(name="tag2") - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "tags": [t2.id, t1.id, 734563]}, @@ -1332,10 +1289,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): 0, tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles"), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "created": created}, @@ -1353,10 +1307,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f, "archive_serial_number": 500}, @@ -1385,10 +1336,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): data_type=CustomField.FieldDataType.STRING, ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", { @@ -1417,10 +1365,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): id=str(uuid.uuid4()), ) - with open( - os.path.join(os.path.dirname(__file__), "samples", "invalid_pdf.pdf"), - "rb", - ) as f: + with (Path(__file__).parent / "samples" / "invalid_pdf.pdf").open("rb") as f: response = self.client.post( "/api/documents/post_document/", {"document": f}, @@ -1437,14 +1382,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): archive_filename="archive.pdf", ) - source_file = os.path.join( - os.path.dirname(__file__), - "samples", - "documents", - "thumbnails", - "0000001.webp", + source_file: Path = ( + Path(__file__).parent + / "samples" + / "documents" + / "thumbnails" + / "0000001.webp" ) - archive_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf") + archive_file: Path = Path(__file__).parent / "samples" / "simple.pdf" shutil.copy(source_file, doc.source_path) shutil.copy(archive_file, doc.archive_path) @@ -1460,8 +1405,8 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): self.assertGreater(len(meta["archive_metadata"]), 0) self.assertEqual(meta["media_filename"], "file.pdf") self.assertEqual(meta["archive_media_filename"], "archive.pdf") - self.assertEqual(meta["original_size"], os.stat(source_file).st_size) - self.assertEqual(meta["archive_size"], os.stat(archive_file).st_size) + self.assertEqual(meta["original_size"], Path(source_file).stat().st_size) + self.assertEqual(meta["archive_size"], Path(archive_file).stat().st_size) response = self.client.get(f"/api/documents/{doc.pk}/metadata/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1477,10 +1422,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): mime_type="application/pdf", ) - shutil.copy( - os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), - doc.source_path, - ) + shutil.copy(Path(__file__).parent / "samples" / "simple.pdf", doc.source_path) response = self.client.get(f"/api/documents/{doc.pk}/metadata/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1939,9 +1881,9 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): def test_get_logs(self): log_data = "test\ntest2\n" - with open(os.path.join(settings.LOGGING_DIR, "mail.log"), "w") as f: + with (Path(settings.LOGGING_DIR) / "mail.log").open("w") as f: f.write(log_data) - with open(os.path.join(settings.LOGGING_DIR, "paperless.log"), "w") as f: + with (Path(settings.LOGGING_DIR) / "paperless.log").open("w") as f: f.write(log_data) response = self.client.get("/api/logs/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1949,7 +1891,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): def test_get_logs_only_when_exist(self): log_data = "test\ntest2\n" - with open(os.path.join(settings.LOGGING_DIR, "paperless.log"), "w") as f: + with (Path(settings.LOGGING_DIR) / "paperless.log").open("w") as f: f.write(log_data) response = self.client.get("/api/logs/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1966,7 +1908,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): def test_get_log(self): log_data = "test\ntest2\n" - with open(os.path.join(settings.LOGGING_DIR, "paperless.log"), "w") as f: + with (Path(settings.LOGGING_DIR) / "paperless.log").open("w") as f: f.write(log_data) response = self.client.get("/api/logs/paperless/") self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/documents/tests/test_api_filter_by_custom_fields.py b/src/documents/tests/test_api_filter_by_custom_fields.py index deb97bf29..70d43dfde 100644 --- a/src/documents/tests/test_api_filter_by_custom_fields.py +++ b/src/documents/tests/test_api_filter_by_custom_fields.py @@ -165,6 +165,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase): self, query: list, reference_predicate: Callable[[DocumentWrapper], bool], + *, match_nothing_ok=False, ): """ diff --git a/src/documents/tests/test_api_permissions.py b/src/documents/tests/test_api_permissions.py index 5de1887b2..3785c8f2a 100644 --- a/src/documents/tests/test_api_permissions.py +++ b/src/documents/tests/test_api_permissions.py @@ -3,6 +3,7 @@ import json from unittest import mock from allauth.mfa.models import Authenticator +from allauth.mfa.totp.internal import auth as totp_auth from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.auth.models import User @@ -488,6 +489,71 @@ class TestApiAuth(DirectoriesMixin, APITestCase): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual(response.data["detail"], "MFA required") + @mock.patch("allauth.mfa.totp.internal.auth.TOTP.validate_code") + def test_get_token_mfa_enabled(self, mock_validate_code): + """ + GIVEN: + - User with MFA enabled + WHEN: + - API request is made to obtain an auth token + THEN: + - MFA code is required + """ + user1 = User.objects.create_user(username="user1") + user1.set_password("password") + user1.save() + + response = self.client.post( + "/api/token/", + data={ + "username": "user1", + "password": "password", + }, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + secret = totp_auth.generate_totp_secret() + totp_auth.TOTP.activate( + user1, + secret, + ) + + # no code + response = self.client.post( + "/api/token/", + data={ + "username": "user1", + "password": "password", + }, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["non_field_errors"][0], "MFA code is required") + + # invalid code + mock_validate_code.return_value = False + response = self.client.post( + "/api/token/", + data={ + "username": "user1", + "password": "password", + "code": "123456", + }, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["non_field_errors"][0], "Invalid MFA code") + + # valid code + mock_validate_code.return_value = True + response = self.client.post( + "/api/token/", + data={ + "username": "user1", + "password": "password", + "code": "123456", + }, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + class TestApiUser(DirectoriesMixin, APITestCase): ENDPOINT = "/api/users/" diff --git a/src/documents/tests/test_bulk_edit.py b/src/documents/tests/test_bulk_edit.py index 03c177343..4a7145d34 100644 --- a/src/documents/tests/test_bulk_edit.py +++ b/src/documents/tests/test_bulk_edit.py @@ -268,7 +268,7 @@ class TestBulkEdit(DirectoriesMixin, TestCase): ) cf3 = CustomField.objects.create( name="cf3", - data_type=CustomField.FieldDataType.STRING, + data_type=CustomField.FieldDataType.DOCUMENTLINK, ) CustomFieldInstance.objects.create( document=self.doc2, @@ -284,7 +284,7 @@ class TestBulkEdit(DirectoriesMixin, TestCase): ) bulk_edit.modify_custom_fields( [self.doc1.id, self.doc2.id], - add_custom_fields={cf2.id: None, cf3.id: "value"}, + add_custom_fields={cf2.id: None, cf3.id: [self.doc3.id]}, remove_custom_fields=[cf.id], ) @@ -301,7 +301,7 @@ class TestBulkEdit(DirectoriesMixin, TestCase): ) self.assertEqual( self.doc1.custom_fields.get(field=cf3).value, - "value", + [self.doc3.id], ) self.assertEqual( self.doc2.custom_fields.count(), @@ -309,13 +309,33 @@ class TestBulkEdit(DirectoriesMixin, TestCase): ) self.assertEqual( self.doc2.custom_fields.get(field=cf3).value, - "value", + [self.doc3.id], + ) + # assert reflect document link + self.assertEqual( + self.doc3.custom_fields.first().value, + [self.doc2.id, self.doc1.id], ) self.async_task.assert_called_once() args, kwargs = self.async_task.call_args self.assertCountEqual(kwargs["document_ids"], [self.doc1.id, self.doc2.id]) + # removal of document link cf, should also remove symmetric link + bulk_edit.modify_custom_fields( + [self.doc3.id], + add_custom_fields={}, + remove_custom_fields=[cf3.id], + ) + self.assertNotIn( + self.doc3.id, + self.doc1.custom_fields.filter(field=cf3).first().value, + ) + self.assertNotIn( + self.doc3.id, + self.doc2.custom_fields.filter(field=cf3).first().value, + ) + def test_delete(self): self.assertEqual(Document.objects.count(), 5) bulk_edit.delete([self.doc1.id, self.doc2.id]) @@ -515,7 +535,12 @@ class TestPDFActions(DirectoriesMixin, TestCase): metadata_document_id = self.doc1.id user = User.objects.create(username="test_user") - result = bulk_edit.merge(doc_ids, None, False, user) + result = bulk_edit.merge( + doc_ids, + metadata_document_id=None, + delete_originals=False, + user=user, + ) expected_filename = ( f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf" @@ -618,7 +643,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): doc_ids = [self.doc2.id] pages = [[1, 2], [3]] user = User.objects.create(username="test_user") - result = bulk_edit.split(doc_ids, pages, False, user) + result = bulk_edit.split(doc_ids, pages, delete_originals=False, user=user) self.assertEqual(mock_consume_file.call_count, 2) consume_file_args, _ = mock_consume_file.call_args self.assertEqual(consume_file_args[1].title, "B (split 2)") diff --git a/src/documents/tests/test_consumer.py b/src/documents/tests/test_consumer.py index a2168c67b..6f576ab24 100644 --- a/src/documents/tests/test_consumer.py +++ b/src/documents/tests/test_consumer.py @@ -236,7 +236,7 @@ class FaultyGenericExceptionParser(_BaseTestParser): raise Exception("Generic exception.") -def fake_magic_from_file(file, mime=False): +def fake_magic_from_file(file, *, mime=False): if mime: if file.name.startswith("invalid_pdf"): return "application/octet-stream" diff --git a/src/documents/tests/test_delayedquery.py b/src/documents/tests/test_delayedquery.py index 1895bd6c6..3ee4fb15d 100644 --- a/src/documents/tests/test_delayedquery.py +++ b/src/documents/tests/test_delayedquery.py @@ -10,7 +10,7 @@ class TestDelayedQuery(TestCase): super().setUp() # all tests run without permission criteria, so has_no_owner query will always # be appended. - self.has_no_owner = query.Or([query.Term("has_owner", False)]) + self.has_no_owner = query.Or([query.Term("has_owner", text=False)]) def _get_testset__id__in(self, param, field): return ( @@ -43,12 +43,12 @@ class TestDelayedQuery(TestCase): def test_get_permission_criteria(self): # tests contains tuples of user instances and the expected filter tests = ( - (None, [query.Term("has_owner", False)]), + (None, [query.Term("has_owner", text=False)]), (User(42, username="foo", is_superuser=True), []), ( User(42, username="foo", is_superuser=False), [ - query.Term("has_owner", False), + query.Term("has_owner", text=False), query.Term("owner_id", 42), query.Term("viewer_id", "42"), ], diff --git a/src/documents/tests/test_management_consumer.py b/src/documents/tests/test_management_consumer.py index 7e2707403..808216d3d 100644 --- a/src/documents/tests/test_management_consumer.py +++ b/src/documents/tests/test_management_consumer.py @@ -93,7 +93,7 @@ class ConsumerThreadMixin(DocumentConsumeDelayMixin): else: print("Consumed a perfectly valid file.") # noqa: T201 - def slow_write_file(self, target, incomplete=False): + def slow_write_file(self, target, *, incomplete=False): with open(self.sample_file, "rb") as f: pdf_bytes = f.read() diff --git a/src/documents/tests/test_management_exporter.py b/src/documents/tests/test_management_exporter.py index 0a79b6cd7..eec2fcd4b 100644 --- a/src/documents/tests/test_management_exporter.py +++ b/src/documents/tests/test_management_exporter.py @@ -188,7 +188,7 @@ class TestExportImport( return manifest - def test_exporter(self, use_filename_format=False): + def test_exporter(self, *, use_filename_format=False): shutil.rmtree(os.path.join(self.dirs.media_dir, "documents")) shutil.copytree( os.path.join(os.path.dirname(__file__), "samples", "documents"), diff --git a/src/documents/tests/test_matchables.py b/src/documents/tests/test_matchables.py index 9ca23e53d..180cf77ed 100644 --- a/src/documents/tests/test_matchables.py +++ b/src/documents/tests/test_matchables.py @@ -23,6 +23,7 @@ class _TestMatchingBase(TestCase): match_algorithm: str, should_match: Iterable[str], no_match: Iterable[str], + *, case_sensitive: bool = False, ): for klass in (Tag, Correspondent, DocumentType): diff --git a/src/documents/views.py b/src/documents/views.py index f98932a6f..f23c1b953 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -15,7 +15,6 @@ from urllib.parse import quote from urllib.parse import urlparse import pathvalidate -from django.apps import apps from django.conf import settings from django.contrib.auth.models import Group from django.contrib.auth.models import User @@ -1609,7 +1608,7 @@ class BulkDownloadView(GenericAPIView): strategy_class = ArchiveOnlyStrategy with zipfile.ZipFile(temp.name, "w", compression) as zipf: - strategy = strategy_class(zipf, follow_filename_format) + strategy = strategy_class(zipf, follow_formatting=follow_filename_format) for document in documents: strategy.add_document(document) @@ -1873,7 +1872,7 @@ class SharedLinkView(View): ) -def serve_file(doc: Document, use_archive: bool, disposition: str): +def serve_file(*, doc: Document, use_archive: bool, disposition: str): if use_archive: file_handle = doc.archive_file filename = doc.get_public_filename(archive=True) @@ -2174,18 +2173,14 @@ class SystemStatusView(PassUserMixin): classifier_status = "WARNING" raise FileNotFoundError(classifier_error) classifier_status = "OK" - task_result_model = apps.get_model("django_celery_results", "taskresult") - result = ( - task_result_model.objects.filter( - task_name="documents.tasks.train_classifier", - status="SUCCESS", + classifier_last_trained = ( + make_aware( + datetime.fromtimestamp(classifier.get_last_checked()), ) - .order_by( - "-date_done", - ) - .first() + if settings.MODEL_FILE.exists() + and classifier.get_last_checked() is not None + else None ) - classifier_last_trained = result.date_done if result else None except Exception as e: if classifier_status is None: classifier_status = "ERROR" diff --git a/src/paperless/consumers.py b/src/paperless/consumers.py index cf1a3b548..c72b58aa7 100644 --- a/src/paperless/consumers.py +++ b/src/paperless/consumers.py @@ -41,4 +41,10 @@ class StatusConsumer(WebsocketConsumer): self.close() else: if self._is_owner_or_unowned(event["data"]): - self.send(json.dumps(event["data"])) + self.send(json.dumps(event)) + + def documents_deleted(self, event): + if not self._authenticated(): + self.close() + else: + self.send(json.dumps(event)) diff --git a/src/paperless/serialisers.py b/src/paperless/serialisers.py index d5acfe465..cd9325c09 100644 --- a/src/paperless/serialisers.py +++ b/src/paperless/serialisers.py @@ -1,11 +1,14 @@ import logging from allauth.mfa.adapter import get_adapter as get_mfa_adapter +from allauth.mfa.models import Authenticator +from allauth.mfa.totp.internal.auth import TOTP from allauth.socialaccount.models import SocialAccount from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.auth.models import User from rest_framework import serializers +from rest_framework.authtoken.serializers import AuthTokenSerializer from paperless.models import ApplicationConfiguration @@ -24,6 +27,36 @@ class ObfuscatedUserPasswordField(serializers.Field): return data +class PaperlessAuthTokenSerializer(AuthTokenSerializer): + code = serializers.CharField( + label="MFA Code", + write_only=True, + required=False, + ) + + def validate(self, attrs): + attrs = super().validate(attrs) + user = attrs.get("user") + code = attrs.get("code") + mfa_adapter = get_mfa_adapter() + if mfa_adapter.is_mfa_enabled(user): + if not code: + raise serializers.ValidationError( + "MFA code is required", + ) + authenticator = Authenticator.objects.get( + user=user, + type=Authenticator.Type.TOTP, + ) + if not TOTP(instance=authenticator).validate_code( + code, + ): + raise serializers.ValidationError( + "Invalid MFA code", + ) + return attrs + + class UserSerializer(serializers.ModelSerializer): password = ObfuscatedUserPasswordField(required=False) user_permissions = serializers.SlugRelatedField( @@ -129,7 +162,7 @@ class SocialAccountSerializer(serializers.ModelSerializer): class ProfileSerializer(serializers.ModelSerializer): - email = serializers.EmailField(allow_null=False) + email = serializers.EmailField(allow_blank=True, required=False) password = ObfuscatedUserPasswordField(required=False, allow_null=False) auth_token = serializers.SlugRelatedField(read_only=True, slug_field="key") social_accounts = SocialAccountSerializer( diff --git a/src/paperless/tests/test_websockets.py b/src/paperless/tests/test_websockets.py index bf838821a..5ba909d1c 100644 --- a/src/paperless/tests/test_websockets.py +++ b/src/paperless/tests/test_websockets.py @@ -5,6 +5,9 @@ from channels.testing import WebsocketCommunicator from django.test import TestCase from django.test import override_settings +from documents.plugins.helpers import DocumentsStatusManager +from documents.plugins.helpers import ProgressManager +from documents.plugins.helpers import ProgressStatusOptions from paperless.asgi import application TEST_CHANNEL_LAYERS = { @@ -22,6 +25,39 @@ class TestWebSockets(TestCase): self.assertFalse(connected) await communicator.disconnect() + @mock.patch("paperless.consumers.StatusConsumer.close") + @mock.patch("paperless.consumers.StatusConsumer._authenticated") + async def test_close_on_no_auth(self, _authenticated, mock_close): + _authenticated.return_value = True + + communicator = WebsocketCommunicator(application, "/ws/status/") + connected, subprotocol = await communicator.connect() + self.assertTrue(connected) + + message = {"type": "status_update", "data": {"task_id": "test"}} + + _authenticated.return_value = False + + channel_layer = get_channel_layer() + await channel_layer.group_send( + "status_updates", + message, + ) + await communicator.receive_nothing() + + mock_close.assert_called_once() + mock_close.reset_mock() + + message = {"type": "documents_deleted", "data": {"documents": [1, 2, 3]}} + + await channel_layer.group_send( + "status_updates", + message, + ) + await communicator.receive_nothing() + + mock_close.assert_called_once() + @mock.patch("paperless.consumers.StatusConsumer._authenticated") async def test_auth(self, _authenticated): _authenticated.return_value = True @@ -33,19 +69,19 @@ class TestWebSockets(TestCase): await communicator.disconnect() @mock.patch("paperless.consumers.StatusConsumer._authenticated") - async def test_receive(self, _authenticated): + async def test_receive_status_update(self, _authenticated): _authenticated.return_value = True communicator = WebsocketCommunicator(application, "/ws/status/") connected, subprotocol = await communicator.connect() self.assertTrue(connected) - message = {"task_id": "test"} + message = {"type": "status_update", "data": {"task_id": "test"}} channel_layer = get_channel_layer() await channel_layer.group_send( "status_updates", - {"type": "status_update", "data": message}, + message, ) response = await communicator.receive_json_from() @@ -53,3 +89,73 @@ class TestWebSockets(TestCase): self.assertEqual(response, message) await communicator.disconnect() + + @mock.patch("paperless.consumers.StatusConsumer._authenticated") + async def test_receive_documents_deleted(self, _authenticated): + _authenticated.return_value = True + + communicator = WebsocketCommunicator(application, "/ws/status/") + connected, subprotocol = await communicator.connect() + self.assertTrue(connected) + + message = {"type": "documents_deleted", "data": {"documents": [1, 2, 3]}} + + channel_layer = get_channel_layer() + await channel_layer.group_send( + "status_updates", + message, + ) + + response = await communicator.receive_json_from() + + self.assertEqual(response, message) + + await communicator.disconnect() + + @mock.patch("channels.layers.InMemoryChannelLayer.group_send") + def test_manager_send_progress(self, mock_group_send): + with ProgressManager(task_id="test") as manager: + manager.send_progress( + ProgressStatusOptions.STARTED, + "Test message", + 1, + 10, + extra_args={ + "foo": "bar", + }, + ) + + message = mock_group_send.call_args[0][1] + + self.assertEqual( + message, + { + "type": "status_update", + "data": { + "filename": None, + "task_id": "test", + "current_progress": 1, + "max_progress": 10, + "status": ProgressStatusOptions.STARTED, + "message": "Test message", + "foo": "bar", + }, + }, + ) + + @mock.patch("channels.layers.InMemoryChannelLayer.group_send") + def test_manager_send_documents_deleted(self, mock_group_send): + with DocumentsStatusManager() as manager: + manager.send_documents_deleted([1, 2, 3]) + + message = mock_group_send.call_args[0][1] + + self.assertEqual( + message, + { + "type": "documents_deleted", + "data": { + "documents": [1, 2, 3], + }, + }, + ) diff --git a/src/paperless/urls.py b/src/paperless/urls.py index c528c5e2a..703a72042 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -14,7 +14,6 @@ from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import RedirectView from django.views.static import serve -from rest_framework.authtoken import views from rest_framework.routers import DefaultRouter from documents.views import BulkDownloadView @@ -50,6 +49,7 @@ from paperless.views import DisconnectSocialAccountView from paperless.views import FaviconView from paperless.views import GenerateAuthTokenView from paperless.views import GroupViewSet +from paperless.views import PaperlessObtainAuthTokenView from paperless.views import ProfileView from paperless.views import SocialAccountProvidersView from paperless.views import TOTPView @@ -157,7 +157,7 @@ urlpatterns = [ ), path( "token/", - views.obtain_auth_token, + PaperlessObtainAuthTokenView.as_view(), ), re_path( "^profile/", diff --git a/src/paperless/version.py b/src/paperless/version.py index 185208da1..b09a20ef4 100644 --- a/src/paperless/version.py +++ b/src/paperless/version.py @@ -1,6 +1,6 @@ from typing import Final -__version__: Final[tuple[int, int, int]] = (2, 14, 6) +__version__: Final[tuple[int, int, int]] = (2, 14, 7) # Version string like X.Y.Z __full_version_str__: Final[str] = ".".join(map(str, __version__)) # Version string like X.Y diff --git a/src/paperless/views.py b/src/paperless/views.py index 03721adf2..6d297c49b 100644 --- a/src/paperless/views.py +++ b/src/paperless/views.py @@ -19,6 +19,7 @@ from django.http import HttpResponseNotFound from django.views.generic import View from django_filters.rest_framework import DjangoFilterBackend from rest_framework.authtoken.models import Token +from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.decorators import action from rest_framework.filters import OrderingFilter from rest_framework.generics import GenericAPIView @@ -35,10 +36,15 @@ from paperless.filters import UserFilterSet from paperless.models import ApplicationConfiguration from paperless.serialisers import ApplicationConfigurationSerializer from paperless.serialisers import GroupSerializer +from paperless.serialisers import PaperlessAuthTokenSerializer from paperless.serialisers import ProfileSerializer from paperless.serialisers import UserSerializer +class PaperlessObtainAuthTokenView(ObtainAuthToken): + serializer_class = PaperlessAuthTokenSerializer + + class StandardPagination(PageNumberPagination): page_size = 25 page_size_query_param = "page_size" @@ -142,7 +148,7 @@ class UserViewSet(ModelViewSet): ).first() if authenticator is not None: delete_and_cleanup(request, authenticator) - return Response(True) + return Response(data=True) else: return HttpResponseNotFound("TOTP not found") @@ -256,7 +262,7 @@ class TOTPView(GenericAPIView): ).first() if authenticator is not None: delete_and_cleanup(request, authenticator) - return Response(True) + return Response(data=True) else: return HttpResponseNotFound("TOTP not found") diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index e25c4f227..cf35ea6cb 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -121,7 +121,7 @@ class MarkReadMailAction(BaseMailAction): return {"seen": False} def post_consume(self, M: MailBox, message_uid: str, parameter: str): - M.flag(message_uid, [MailMessageFlags.SEEN], True) + M.flag(message_uid, [MailMessageFlags.SEEN], value=True) class MoveMailAction(BaseMailAction): @@ -142,7 +142,7 @@ class FlagMailAction(BaseMailAction): return {"flagged": False} def post_consume(self, M: MailBox, message_uid: str, parameter: str): - M.flag(message_uid, [MailMessageFlags.FLAGGED], True) + M.flag(message_uid, [MailMessageFlags.FLAGGED], value=True) class TagMailAction(BaseMailAction): @@ -150,7 +150,7 @@ class TagMailAction(BaseMailAction): A mail action that tags mails after processing. """ - def __init__(self, parameter: str, supports_gmail_labels: bool): + def __init__(self, parameter: str, *, supports_gmail_labels: bool): # The custom tag should look like "apple:" if "apple:" in parameter.lower(): _, self.color = parameter.split(":") @@ -188,19 +188,19 @@ class TagMailAction(BaseMailAction): M.flag( message_uid, set(itertools.chain(*APPLE_MAIL_TAG_COLORS.values())), - False, + value=False, ) # Set new $MailFlagBits - M.flag(message_uid, APPLE_MAIL_TAG_COLORS.get(self.color), True) + M.flag(message_uid, APPLE_MAIL_TAG_COLORS.get(self.color), value=True) # Set the general \Flagged # This defaults to the "red" flag in AppleMail and # "stars" in Thunderbird or GMail - M.flag(message_uid, [MailMessageFlags.FLAGGED], True) + M.flag(message_uid, [MailMessageFlags.FLAGGED], value=True) elif self.keyword: - M.flag(message_uid, [self.keyword], True) + M.flag(message_uid, [self.keyword], value=True) else: raise MailError("No keyword specified.") @@ -268,7 +268,7 @@ def apply_mail_action( mailbox_login(M, account) M.folder.set(rule.folder) - action = get_rule_action(rule, supports_gmail_labels) + action = get_rule_action(rule, supports_gmail_labels=supports_gmail_labels) try: action.post_consume(M, message_uid, rule.action_parameter) except errors.ImapToolsError: @@ -356,7 +356,7 @@ def queue_consumption_tasks( ).delay() -def get_rule_action(rule: MailRule, supports_gmail_labels: bool) -> BaseMailAction: +def get_rule_action(rule: MailRule, *, supports_gmail_labels: bool) -> BaseMailAction: """ Returns a BaseMailAction instance for the given rule. """ @@ -370,12 +370,15 @@ def get_rule_action(rule: MailRule, supports_gmail_labels: bool) -> BaseMailActi elif rule.action == MailRule.MailAction.MARK_READ: return MarkReadMailAction() elif rule.action == MailRule.MailAction.TAG: - return TagMailAction(rule.action_parameter, supports_gmail_labels) + return TagMailAction( + rule.action_parameter, + supports_gmail_labels=supports_gmail_labels, + ) else: raise NotImplementedError("Unknown action.") # pragma: no cover -def make_criterias(rule: MailRule, supports_gmail_labels: bool): +def make_criterias(rule: MailRule, *, supports_gmail_labels: bool): """ Returns criteria to be applied to MailBox.fetch for the given rule. """ @@ -393,7 +396,10 @@ def make_criterias(rule: MailRule, supports_gmail_labels: bool): if rule.filter_body: criterias["body"] = rule.filter_body - rule_query = get_rule_action(rule, supports_gmail_labels).get_criteria() + rule_query = get_rule_action( + rule, + supports_gmail_labels=supports_gmail_labels, + ).get_criteria() if isinstance(rule_query, dict): if len(rule_query) or len(criterias): return AND(**rule_query, **criterias) @@ -563,7 +569,7 @@ class MailAccountHandler(LoggingMixin): total_processed_files += self._handle_mail_rule( M, rule, - supports_gmail_labels, + supports_gmail_labels=supports_gmail_labels, ) except Exception as e: self.log.exception( @@ -588,6 +594,7 @@ class MailAccountHandler(LoggingMixin): self, M: MailBox, rule: MailRule, + *, supports_gmail_labels: bool, ): folders = [rule.folder] @@ -616,7 +623,7 @@ class MailAccountHandler(LoggingMixin): f"does not exist in account {rule.account}", ) from err - criterias = make_criterias(rule, supports_gmail_labels) + criterias = make_criterias(rule, supports_gmail_labels=supports_gmail_labels) self.log.debug( f"Rule {rule}: Searching folder with criteria {criterias}", diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py index 2311c3009..a73f9cf34 100644 --- a/src/paperless_mail/tests/test_mail.py +++ b/src/paperless_mail/tests/test_mail.py @@ -124,7 +124,7 @@ class BogusMailBox(AbstractContextManager): if username != self.USERNAME or access_token != self.ACCESS_TOKEN: raise MailboxLoginError("BAD", "OK") - def fetch(self, criteria, mark_seen, charset="", bulk=True): + def fetch(self, criteria, mark_seen, charset="", *, bulk=True): msg = self.messages criteria = str(criteria).strip("()").split(" ") @@ -190,7 +190,7 @@ class BogusMailBox(AbstractContextManager): raise Exception -def fake_magic_from_buffer(buffer, mime=False): +def fake_magic_from_buffer(buffer, *, mime=False): if mime: if "PDF" in str(buffer): return "application/pdf" @@ -206,6 +206,7 @@ class MessageBuilder: def create_message( self, + *, attachments: int | list[_AttachmentDef] = 1, body: str = "", subject: str = "the subject", @@ -783,12 +784,18 @@ class TestMail( ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 2, + ) self.mail_account_handler.handle_mail_account(account) self.mailMocker.apply_mail_actions() - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 0, + ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) def test_handle_mail_account_delete(self): @@ -853,7 +860,7 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), + len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)), 2, ) @@ -861,7 +868,7 @@ class TestMail( self.mailMocker.apply_mail_actions() self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), + len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)), 1, ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) @@ -934,7 +941,12 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNKEYWORD processed", False)), + len( + self.mailMocker.bogus_mailbox.fetch( + "UNKEYWORD processed", + mark_seen=False, + ), + ), 2, ) @@ -943,7 +955,12 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNKEYWORD processed", False)), + len( + self.mailMocker.bogus_mailbox.fetch( + "UNKEYWORD processed", + mark_seen=False, + ), + ), 0, ) @@ -967,12 +984,18 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) criteria = NOT(gmail_label="processed") - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch(criteria, False)), 2) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch(criteria, mark_seen=False)), + 2, + ) self.mail_account_handler.handle_mail_account(account) self.mailMocker.apply_mail_actions() - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch(criteria, False)), 0) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch(criteria, mark_seen=False)), + 0, + ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) def test_tag_mail_action_applemail_wrong_input(self): @@ -980,7 +1003,7 @@ class TestMail( MailError, TagMailAction, "apple:black", - False, + supports_gmail_labels=False, ) def test_handle_mail_account_tag_applemail(self): @@ -1002,7 +1025,7 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), + len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)), 2, ) @@ -1010,7 +1033,7 @@ class TestMail( self.mailMocker.apply_mail_actions() self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), + len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)), 0, ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) @@ -1324,13 +1347,19 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.mailMocker._queue_consumption_tasks_mock.assert_not_called() - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 2, + ) self.mail_account_handler.handle_mail_account(account) self.mailMocker.apply_mail_actions() self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 2) - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 0, + ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) def test_auth_plain_fallback_fails_still(self): @@ -1390,13 +1419,19 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 0) - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 2, + ) self.mail_account_handler.handle_mail_account(account) self.mailMocker.apply_mail_actions() self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 2) - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 0, + ) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) def test_disabled_rule(self): @@ -1425,12 +1460,15 @@ class TestMail( self.mailMocker.apply_mail_actions() self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) - self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), + 2, + ) self.mail_account_handler.handle_mail_account(account) self.mailMocker.apply_mail_actions() self.assertEqual( - len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)), 2, ) # still 2 diff --git a/src/paperless_tesseract/parsers.py b/src/paperless_tesseract/parsers.py index e7968a61e..a8be899f5 100644 --- a/src/paperless_tesseract/parsers.py +++ b/src/paperless_tesseract/parsers.py @@ -214,6 +214,7 @@ class RasterisedDocumentParser(DocumentParser): mime_type, output_file, sidecar_file, + *, safe_fallback=False, ): if TYPE_CHECKING: