Compare commits

...

11 Commits

Author SHA1 Message Date
dependabot[bot]
4e7a1d3935
docker(deps): Bump astral-sh/uv
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.7.9-python3.12-bookworm-slim to 0.7.13-python3.12-bookworm-slim.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.7.9...0.7.13)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.7.13-python3.12-bookworm-slim
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-19 15:30:43 +00:00
shamoon
52b95f2b62
QoL: log version at startup, show backend vs frontend mismatch in system status (#10214) 2025-06-19 08:29:34 -07:00
shamoon
83391af866
Fix: more api fixes (#10204) 2025-06-19 08:28:41 -07:00
GitHub Actions
0a1786f39b Auto translate strings 2025-06-19 14:50:29 +00:00
shamoon
3b069ac034
Fix: restore expected pre-2.16 scheduled workflow offset behavior (#10218) 2025-06-19 14:47:54 +00:00
Sebastian Steinbeißer
07882b918b
Chore: switch from os.path to pathlib.Path (#9933)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-06-18 17:16:59 +00:00
shamoon
cc5ba71f06
Chore: remove spaces from run log 2025-06-17 16:02:45 -07:00
GitHub Actions
f16b8fbe2a Auto translate strings 2025-06-17 19:02:55 +00:00
shamoon
fe54d99356
Chore: bump angular to 19.12.14 (#10212) 2025-06-17 12:01:15 -07:00
GitHub Actions
a49efb07ea Auto translate strings 2025-06-17 05:46:29 +00:00
shamoon
e4fd008441
Fix: fix some API crashes (#10196) 2025-06-16 22:44:39 -07:00
34 changed files with 678 additions and 270 deletions

View File

@ -21,7 +21,7 @@ ARG PNGX_TAG_VERSION=
RUN set -eux && \
case "${PNGX_TAG_VERSION}" in \
dev|beta|fix*|feature*) \
sed -i -E "s/version: '([0-9\.]+)'/version: '\1 #${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \
sed -i -E "s/tag: '([a-z\.]+)'/tag: '${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \
;; \
esac
@ -32,7 +32,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs
# Comments:
# - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.7.9-python3.12-bookworm-slim AS s6-overlay-base
FROM ghcr.io/astral-sh/uv:0.7.13-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6

View File

@ -408,7 +408,7 @@ Currently, there are three events that correspond to workflow trigger 'types':
tags, doc type, or correspondent.
4. **Scheduled**: a scheduled trigger that can be used to run workflows at a specific time. The date used can be either the document
added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date (positive
offsets will trigger before the date, negative offsets will trigger after).
offsets will trigger after the date, negative offsets will trigger before).
The following flow diagram illustrates the three document trigger types:

View File

@ -221,22 +221,12 @@ lint.per-file-ignores."src/documents/parsers.py" = [
lint.per-file-ignores."src/documents/signals/handlers.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/views.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/checks.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/settings.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_mail/mail.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [
"PTH",
"RUF001",
] # TODO PTH Enable & remove
]
lint.isort.force-single-line = true
[tool.pytest.ini_options]

View File

@ -8,7 +8,7 @@ module.exports = {
'abstract-paperless-service',
],
transformIgnorePatterns: [
`<rootDir>/node_modules/.pnpm/(?!.*\\.mjs$|lodash-es)`,
`<rootDir>/node_modules/.pnpm/(?!.*\\.mjs$|lodash-es|@angular\\+common.*locales)`,
],
moduleNameMapper: {
'^src/(.*)': '<rootDir>/src/$1',

View File

@ -5,14 +5,14 @@
<trans-unit id="ngb.alert.close" datatype="html">
<source>Close</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/alert/alert.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/alert/alert.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.carousel.slide-number" datatype="html">
<source> Slide <x id="INTERPOLATION" equiv-text="ueryList&lt;NgbSli"/> of <x id="INTERPOLATION_1" equiv-text="EventSource = N"/> </source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/carousel/carousel.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">132,136</context>
</context-group>
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
@ -20,212 +20,212 @@
<trans-unit id="ngb.carousel.previous" datatype="html">
<source>Previous</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/carousel/carousel.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">158,160</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.carousel.next" datatype="html">
<source>Next</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/carousel/carousel.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">199</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.previous-month" datatype="html">
<source>Previous month</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">77,79</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">102</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.next-month" datatype="html">
<source>Next month</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">102</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">102</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.HH" datatype="html">
<source>HH</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.toast.close-aria" datatype="html">
<source>Close</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.select-month" datatype="html">
<source>Select month</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.first" datatype="html">
<source>««</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.hours" datatype="html">
<source>Hours</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.previous" datatype="html">
<source>«</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.MM" datatype="html">
<source>MM</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.next" datatype="html">
<source>»</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.select-year" datatype="html">
<source>Select year</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.minutes" datatype="html">
<source>Minutes</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.last" datatype="html">
<source>»»</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.first-aria" datatype="html">
<source>First</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.increment-hours" datatype="html">
<source>Increment hours</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.previous-aria" datatype="html">
<source>Previous</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.decrement-hours" datatype="html">
<source>Decrement hours</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.next-aria" datatype="html">
<source>Next</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.increment-minutes" datatype="html">
<source>Increment minutes</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.last-aria" datatype="html">
<source>Last</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.decrement-minutes" datatype="html">
<source>Decrement minutes</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.SS" datatype="html">
<source>SS</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.seconds" datatype="html">
<source>Seconds</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.increment-seconds" datatype="html">
<source>Increment seconds</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.decrement-seconds" datatype="html">
<source>Decrement seconds</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.PM" datatype="html">
<source><x id="INTERPOLATION"/></source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/ngb-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/ngb-config.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
@ -233,7 +233,7 @@
<source><x id="INTERPOLATION" equiv-text="barConfig);
pu"/></source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.13_@angular+core@19.2.14_rxjs@7._ce5a45f3b9d5ca136f928f24177f8d04/node_modules/src/progressbar/progressbar.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@18.0.0_@angular+common@19.2.14_@angular+core@19.2.14_rxjs@7._f368c1e2c12c71d00515f6f30d8f21d1/node_modules/src/progressbar/progressbar.ts</context>
<context context-type="linenumber">41,42</context>
</context-group>
</trans-unit>
@ -4648,8 +4648,8 @@
<context context-type="linenumber">128</context>
</context-group>
</trans-unit>
<trans-unit id="1488741788768120127" datatype="html">
<source>Positive values will trigger the workflow before the date, negative values after.</source>
<trans-unit id="7715068346263636924" datatype="html">
<source>Positive values will trigger after the date, negative values before.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">132</context>

View File

@ -12,7 +12,7 @@
"private": true,
"dependencies": {
"@angular/cdk": "^19.2.14",
"@angular/common": "~19.2.13",
"@angular/common": "~19.2.14",
"@angular/compiler": "~19.2.14",
"@angular/core": "~19.2.14",
"@angular/forms": "~19.2.14",

130
src-ui/pnpm-lock.yaml generated
View File

@ -10,10 +10,10 @@ importers:
dependencies:
'@angular/cdk':
specifier: ^19.2.14
version: 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common':
specifier: ~19.2.13
version: 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
specifier: ~19.2.14
version: 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/compiler':
specifier: ~19.2.14
version: 19.2.14
@ -22,28 +22,28 @@ importers:
version: 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/forms':
specifier: ~19.2.14
version: 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/localize':
specifier: ~19.2.14
version: 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)
'@angular/platform-browser':
specifier: ~19.2.14
version: 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
'@angular/platform-browser-dynamic':
specifier: ~19.2.14
version: 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
'@angular/router':
specifier: ~19.2.14
version: 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@ng-bootstrap/ng-bootstrap':
specifier: ^18.0.0
version: 18.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@popperjs/core@2.11.8)(rxjs@7.8.2)
version: 18.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@popperjs/core@2.11.8)(rxjs@7.8.2)
'@ng-select/ng-select':
specifier: ^14.9.0
version: 14.9.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))
version: 14.9.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))
'@ngneat/dirty-check-forms':
specifier: ^3.0.3
version: 3.0.3(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/router@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(lodash-es@4.17.21)(rxjs@7.8.2)
version: 3.0.3(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(lodash-es@4.17.21)(rxjs@7.8.2)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@ -61,19 +61,19 @@ importers:
version: 10.4.0
ngx-bootstrap-icons:
specifier: ^1.9.3
version: 1.9.3(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
version: 1.9.3(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
ngx-color:
specifier: ^10.0.0
version: 10.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
version: 10.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
ngx-cookie-service:
specifier: ^19.1.2
version: 19.1.2(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
version: 19.1.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
ngx-device-detector:
specifier: ^9.0.0
version: 9.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
version: 9.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
ngx-ui-tour-ng-bootstrap:
specifier: ^16.0.0
version: 16.0.0(f0e74b8bab83f30cf77f70d2cc04d4ae)
version: 16.0.0(b22d6d97efbc9cb8f9e09ff61a244f2e)
rxjs:
specifier: ^7.8.2
version: 7.8.2
@ -95,7 +95,7 @@ importers:
version: 19.0.1(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@types/node@22.15.29)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jiti@1.21.7)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.7.0))(yaml@2.7.0)
'@angular-builders/jest':
specifier: ^19.0.1
version: 19.0.1(a3a7a7b7cf5c06373f7925dd32bde0cb)
version: 19.0.1(4b49eb59c0a92e3dbc1542019395d573)
'@angular-devkit/build-angular':
specifier: ^19.2.14
version: 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@types/node@22.15.29)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jiti@1.21.7)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.7.0))(yaml@2.7.0)
@ -161,7 +161,7 @@ importers:
version: 16.0.0
jest-preset-angular:
specifier: ^14.5.5
version: 14.5.5(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4)
version: 14.5.5(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4)
jest-websocket-mock:
specifier: ^2.5.0
version: 2.5.0
@ -386,11 +386,11 @@ packages:
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'}
hasBin: true
'@angular/common@19.2.13':
resolution: {integrity: sha512-k7I4bLH+bgI02VL81MaL0NcZPfVl153KAiARwk+ZlkmQjMnWlmsAHQ6054SWoNEXwP855ATR6YYDVqJh8TZaqw==}
'@angular/common@19.2.14':
resolution: {integrity: sha512-NcNklcuyqaTjOVGf7aru8APX9mjsnZ01gFZrn47BxHozhaR0EMRrotYQTdi8YdVjPkeYFYanVntSLfhyobq/jg==}
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0}
peerDependencies:
'@angular/core': 19.2.13
'@angular/core': 19.2.14
rxjs: ^6.5.3 || ^7.4.0
'@angular/compiler-cli@19.2.14':
@ -6141,7 +6141,7 @@ snapshots:
- webpack-cli
- yaml
'@angular-builders/jest@19.0.1(a3a7a7b7cf5c06373f7925dd32bde0cb)':
'@angular-builders/jest@19.0.1(4b49eb59c0a92e3dbc1542019395d573)':
dependencies:
'@angular-builders/common': 3.0.1(@types/node@22.15.29)(chokidar@4.0.3)(typescript@5.5.4)
'@angular-devkit/architect': 0.1902.8(chokidar@4.0.3)
@ -6149,9 +6149,9 @@ snapshots:
'@angular-devkit/core': 19.2.14(chokidar@4.0.3)
'@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/platform-browser-dynamic': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
'@angular/platform-browser-dynamic': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4))
jest-preset-angular: 14.5.4(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4)
jest-preset-angular: 14.5.4(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4)
lodash: 4.17.21
transitivePeerDependencies:
- '@babel/core'
@ -6422,9 +6422,9 @@ snapshots:
- tsx
- yaml
'@angular/cdk@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
'@angular/cdk@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
parse5: 7.3.0
rxjs: 7.8.2
@ -6454,7 +6454,7 @@ snapshots:
- chokidar
- supports-color
'@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
'@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
dependencies:
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
rxjs: 7.8.2
@ -6485,11 +6485,11 @@ snapshots:
tslib: 2.8.1
zone.js: 0.15.1
'@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
'@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/platform-browser': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
'@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
rxjs: 7.8.2
tslib: 2.8.1
@ -6504,25 +6504,25 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))':
'@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/compiler': 19.2.14
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/platform-browser': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
'@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
tslib: 2.8.1
'@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))':
'@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
tslib: 2.8.1
'@angular/router@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
'@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/platform-browser': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
'@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))
rxjs: 7.8.2
tslib: 2.8.1
@ -8103,28 +8103,28 @@ snapshots:
'@napi-rs/nice-win32-x64-msvc': 1.0.1
optional: true
'@ng-bootstrap/ng-bootstrap@18.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@popperjs/core@2.11.8)(rxjs@7.8.2)':
'@ng-bootstrap/ng-bootstrap@18.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@popperjs/core@2.11.8)(rxjs@7.8.2)':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/forms': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/forms': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/localize': 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)
'@popperjs/core': 2.11.8
rxjs: 7.8.2
tslib: 2.8.1
'@ng-select/ng-select@14.9.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))':
'@ng-select/ng-select@14.9.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))':
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/forms': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/forms': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
tslib: 2.8.1
'@ngneat/dirty-check-forms@3.0.3(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/router@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(lodash-es@4.17.21)(rxjs@7.8.2)':
'@ngneat/dirty-check-forms@3.0.3(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(lodash-es@4.17.21)(rxjs@7.8.2)':
dependencies:
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/forms': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/router': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/forms': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/router': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
lodash-es: 4.17.21
rxjs: 7.8.2
tslib: 2.8.1
@ -10471,11 +10471,11 @@ snapshots:
optionalDependencies:
jest-resolve: 29.7.0
jest-preset-angular@14.5.4(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4):
jest-preset-angular@14.5.4(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4):
dependencies:
'@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/platform-browser-dynamic': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
'@angular/platform-browser-dynamic': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
bs-logger: 0.2.6
esbuild-wasm: 0.25.3
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4))
@ -10497,11 +10497,11 @@ snapshots:
- supports-color
- utf-8-validate
jest-preset-angular@14.5.5(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4):
jest-preset-angular@14.5.5(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4)))(jsdom@20.0.3)(typescript@5.5.4):
dependencies:
'@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/platform-browser-dynamic': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
'@angular/platform-browser-dynamic': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))
bs-logger: 0.2.6
esbuild-wasm: 0.25.2
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.5.4))
@ -11093,46 +11093,46 @@ snapshots:
pdfjs-dist: 4.8.69
tslib: 2.8.1
ngx-bootstrap-icons@1.9.3(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
ngx-bootstrap-icons@1.9.3(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
tslib: 2.8.1
ngx-color@10.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
ngx-color@10.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@ctrl/tinycolor': 4.1.0
material-colors: 1.2.6
tslib: 2.8.1
ngx-cookie-service@19.1.2(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
ngx-cookie-service@19.1.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
tslib: 2.8.1
ngx-device-detector@9.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
ngx-device-detector@9.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)):
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
tslib: 2.8.1
ngx-ui-tour-core@14.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2):
ngx-ui-tour-core@14.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2):
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@angular/router': 19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
'@angular/router': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
rxjs: 7.8.2
tslib: 2.8.1
ngx-ui-tour-ng-bootstrap@16.0.0(f0e74b8bab83f30cf77f70d2cc04d4ae):
ngx-ui-tour-ng-bootstrap@16.0.0(b22d6d97efbc9cb8f9e09ff61a244f2e):
dependencies:
'@angular/common': 19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
'@ng-bootstrap/ng-bootstrap': 18.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@popperjs/core@2.11.8)(rxjs@7.8.2)
ngx-ui-tour-core: 14.0.0(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.13(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2)
'@ng-bootstrap/ng-bootstrap': 18.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14))(@popperjs/core@2.11.8)(rxjs@7.8.2)
ngx-ui-tour-core: 14.0.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2)
tslib: 2.8.1
transitivePeerDependencies:
- '@angular/router'

View File

@ -74,7 +74,6 @@ export class AppFrameComponent
extends ComponentWithPermissions
implements OnInit, ComponentCanDeactivate
{
versionString = `${environment.appTitle} ${environment.version}`
appRemoteVersion: AppRemoteVersion
isMenuCollapsed: boolean = true
@ -142,6 +141,10 @@ export class AppFrameComponent
}, 200) // slightly longer than css animation for slim sidebar
}
get versionString(): string {
return `${environment.appTitle} v${this.settingsService.get(SETTINGS_KEYS.VERSION)}${environment.production ? '' : ` #${environment.tag}`}`
}
get customAppTitle(): string {
return this.settingsService.get(SETTINGS_KEYS.APP_TITLE)
}

View File

@ -129,7 +129,7 @@
formControlName="schedule_offset_days"
[showAdd]="false"
[error]="error?.schedule_offset_days"
hint="Positive values will trigger the workflow before the date, negative values after."
hint="Positive values will trigger after the date, negative values before."
i18n-hint
></pngx-input-number>
</div>

View File

@ -20,7 +20,18 @@
<div class="card-body">
<dl class="card-text">
<dt i18n>Paperless-ngx Version</dt>
<dd>{{status.pngx_version}}</dd>
<dd>
{{status.pngx_version}}
@if (versionMismatch) {
<button class="btn btn-sm d-inline align-items-center btn-dark text-uppercase small" [ngbPopover]="versionPopover" triggers="click mouseenter:mouseleave">
<i-bs name="exclamation-triangle-fill" class="text-danger lh-1"></i-bs>
</button>
}
<ng-template #versionPopover>
Frontend version: {{frontendVersion}}<br>
Backend version: {{status.pngx_version}}
</ng-template>
</dd>
<dt i18n>Install Type</dt>
<dd>{{status.install_type}}</dd>
<dt i18n>Server OS</dt>

View File

@ -1,3 +1,18 @@
// Mock production environment for testing
jest.mock('src/environments/environment', () => ({
environment: {
production: true,
apiBaseUrl: 'http://localhost:8000/api/',
apiVersion: '9',
appTitle: 'Paperless-ngx',
tag: 'prod',
version: '2.4.3',
webSocketHost: 'localhost:8000',
webSocketProtocol: 'ws:',
webSocketBaseUrl: '/ws/',
},
}))
import { Clipboard } from '@angular/cdk/clipboard'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
@ -142,4 +157,15 @@ describe('SystemStatusDialogComponent', () => {
`Task ${PaperlessTaskName.IndexOptimize} started`
)
})
it('shoduld handle version mismatch', () => {
component.frontendVersion = '2.4.2'
component.ngOnInit()
expect(component.versionMismatch).toBeTruthy()
expect(component.status.pngx_version).toContain('(frontend: 2.4.2)')
component.frontendVersion = '2.4.3'
component.status.pngx_version = '2.4.3'
component.ngOnInit()
expect(component.versionMismatch).toBeFalsy()
})
})

View File

@ -1,5 +1,5 @@
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
import { Component } from '@angular/core'
import { Component, OnInit } from '@angular/core'
import {
NgbActiveModal,
NgbModalModule,
@ -18,6 +18,7 @@ import { PermissionsService } from 'src/app/services/permissions.service'
import { SystemStatusService } from 'src/app/services/system-status.service'
import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
@Component({
selector: 'pngx-system-status-dialog',
@ -33,10 +34,12 @@ import { ToastService } from 'src/app/services/toast.service'
NgxBootstrapIconsModule,
],
})
export class SystemStatusDialogComponent {
export class SystemStatusDialogComponent implements OnInit {
public SystemStatusItemStatus = SystemStatusItemStatus
public PaperlessTaskName = PaperlessTaskName
public status: SystemStatus
public frontendVersion: string = environment.version
public versionMismatch: boolean = false
public copied: boolean = false
@ -55,6 +58,17 @@ export class SystemStatusDialogComponent {
private permissionsService: PermissionsService
) {}
public ngOnInit() {
this.versionMismatch =
environment.production &&
this.status.pngx_version &&
this.frontendVersion &&
this.status.pngx_version !== this.frontendVersion
if (this.versionMismatch) {
this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})`
}
}
public close() {
this.activeModal.close()
}

View File

@ -20,6 +20,7 @@ export enum GlobalSearchType {
export const PAPERLESS_GREEN_HEX = '#17541f'
export const SETTINGS_KEYS = {
VERSION: 'version',
LANGUAGE: 'language',
APP_LOGO: 'app_logo',
APP_TITLE: 'app_title',
@ -76,6 +77,11 @@ export const SETTINGS_KEYS = {
}
export const SETTINGS: UiSetting[] = [
{
key: SETTINGS_KEYS.VERSION,
type: 'string',
default: '',
},
{
key: SETTINGS_KEYS.LANGUAGE,
type: 'string',

View File

@ -5,6 +5,7 @@ export const environment = {
apiBaseUrl: document.baseURI + 'api/',
apiVersion: '9', // match src/paperless/settings.py
appTitle: 'Paperless-ngx',
tag: 'prod',
version: '2.16.3',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',

View File

@ -7,6 +7,7 @@ export const environment = {
apiBaseUrl: 'http://localhost:8000/api/',
apiVersion: '9',
appTitle: 'Paperless-ngx',
tag: 'dev',
version: 'DEVELOPMENT',
webSocketHost: 'localhost:8000',
webSocketProtocol: 'ws:',

View File

@ -360,9 +360,9 @@ class OwnedObjectSerializer(
shared_object_pks = self.get_shared_object_pks([obj])
return obj.owner == self.user and obj.id in shared_object_pks
permissions = SerializerMethodField(read_only=True)
user_can_change = SerializerMethodField(read_only=True)
is_shared_by_requester = SerializerMethodField(read_only=True)
permissions = SerializerMethodField(read_only=True, required=False)
user_can_change = SerializerMethodField(read_only=True, required=False)
is_shared_by_requester = SerializerMethodField(read_only=True, required=False)
set_permissions = SetPermissionsSerializer(
label="Set permissions",
@ -1189,7 +1189,6 @@ class SavedViewSerializer(OwnedObjectSerializer):
"owner",
"permissions",
"user_can_change",
"set_permissions",
]
def validate(self, attrs):
@ -1754,6 +1753,8 @@ class StoragePathSerializer(MatchingModelSerializer, OwnedObjectSerializer):
class UiSettingsViewSerializer(serializers.ModelSerializer):
settings = serializers.DictField(required=False, allow_null=True)
class Meta:
model = UiSettings
depth = 1
@ -2020,8 +2021,9 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer):
):
attrs["filter_path"] = None
trigger_type = attrs.get("type", getattr(self.instance, "type", None))
if (
attrs["type"] == WorkflowTrigger.WorkflowTriggerType.CONSUMPTION
trigger_type == WorkflowTrigger.WorkflowTriggerType.CONSUMPTION
and "filter_mailrule" not in attrs
and ("filter_filename" not in attrs or attrs["filter_filename"] is None)
and ("filter_path" not in attrs or attrs["filter_path"] is None)
@ -2193,7 +2195,7 @@ class WorkflowSerializer(serializers.ModelSerializer):
set_triggers = []
set_actions = []
if triggers is not None:
if triggers is not None and triggers is not serializers.empty:
for trigger in triggers:
filter_has_tags = trigger.pop("filter_has_tags", None)
trigger_instance, _ = WorkflowTrigger.objects.update_or_create(
@ -2204,7 +2206,7 @@ class WorkflowSerializer(serializers.ModelSerializer):
trigger_instance.filter_has_tags.set(filter_has_tags)
set_triggers.append(trigger_instance)
if actions is not None:
if actions is not None and actions is not serializers.empty:
for action in actions:
assign_tags = action.pop("assign_tags", None)
assign_view_users = action.pop("assign_view_users", None)
@ -2286,14 +2288,16 @@ class WorkflowSerializer(serializers.ModelSerializer):
set_actions.append(action_instance)
if triggers is not serializers.empty:
instance.triggers.set(set_triggers)
if actions is not serializers.empty:
instance.actions.set(set_actions)
instance.save()
def prune_triggers_and_actions(self):
"""
ManyToMany fields dont support e.g. on_delete so we need to discard unattached
triggers and actionas manually
triggers and actions manually
"""
for trigger in WorkflowTrigger.objects.all():
if trigger.workflows.all().count() == 0:
@ -2320,16 +2324,12 @@ class WorkflowSerializer(serializers.ModelSerializer):
return instance
def update(self, instance: Workflow, validated_data) -> Workflow:
if "triggers" in validated_data:
triggers = validated_data.pop("triggers")
if "actions" in validated_data:
actions = validated_data.pop("actions")
triggers = validated_data.pop("triggers", serializers.empty)
actions = validated_data.pop("actions", serializers.empty)
instance = super().update(instance, validated_data)
self.update_triggers_and_actions(instance, triggers, actions)
self.prune_triggers_and_actions()
return instance

View File

@ -420,7 +420,7 @@ def check_scheduled_workflows():
trigger: WorkflowTrigger
for trigger in schedule_triggers:
documents = Document.objects.none()
offset_td = datetime.timedelta(days=-trigger.schedule_offset_days)
offset_td = datetime.timedelta(days=trigger.schedule_offset_days)
threshold = now - offset_td
logger.debug(
f"Trigger {trigger.id}: checking if (date + {offset_td}) <= now ({now})",

View File

@ -167,3 +167,25 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
},
)
self.assertFalse(Path(old_logo.path).exists())
def test_create_not_allowed(self):
"""
GIVEN:
- API request to create a new app config
WHEN:
- API is called
THEN:
- Correct HTTP response
- No new config is created
"""
response = self.client.post(
self.ENDPOINT,
json.dumps(
{
"output_type": "pdf",
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
self.assertEqual(ApplicationConfiguration.objects.count(), 1)

View File

@ -1278,3 +1278,34 @@ class TestBulkEditObjectPermissions(APITestCase):
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
class TestFullPermissionsFlag(APITestCase):
def setUp(self):
super().setUp()
self.admin = User.objects.create_superuser(username="admin")
def test_full_perms_flag(self):
"""
GIVEN:
- API request to list documents
WHEN:
- full_perms flag is set to true, 1, false, or a random value
THEN:
- Permissions field is included or excluded accordingly
"""
self.client.force_authenticate(self.admin)
Document.objects.create(title="Doc", checksum="xyz", owner=self.admin)
resp = self.client.get("/api/documents/?full_perms=true")
self.assertIn("permissions", resp.data["results"][0])
resp = self.client.get("/api/documents/?full_perms=1")
self.assertIn("permissions", resp.data["results"][0])
resp = self.client.get("/api/documents/?full_perms=false")
self.assertNotIn("permissions", resp.data["results"][0])
resp = self.client.get("/api/documents/?full_perms=garbage")
self.assertNotIn("permissions", resp.data["results"][0])

View File

@ -7,6 +7,7 @@ from rest_framework import status
from rest_framework.test import APITestCase
from documents.tests.utils import DirectoriesMixin
from paperless.version import __full_version_str__
class TestApiUiSettings(DirectoriesMixin, APITestCase):
@ -39,6 +40,7 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase):
self.assertDictEqual(
response.data["settings"],
{
"version": __full_version_str__,
"app_title": None,
"app_logo": None,
"auditlog_enabled": True,
@ -117,6 +119,30 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_settings_must_be_dict(self):
"""
GIVEN:
- API request to update ui_settings with settings not being a dict
WHEN:
- API is called
THEN:
- Correct HTTP 400 response
"""
response = self.client.post(
self.ENDPOINT,
json.dumps(
{
"settings": "not a dict",
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(
"Expected a dictionary",
str(response.data["settings"]),
)
@override_settings(
OAUTH_CALLBACK_BASE_URL="http://localhost:8000",
GMAIL_OAUTH_CLIENT_ID="abc123",

View File

@ -394,6 +394,50 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
self.assertEqual(workflow.triggers.first().filter_has_tags.first(), self.t1)
self.assertEqual(workflow.actions.first().assign_title, "Action New Title")
def test_api_update_workflow_no_trigger_actions(self):
"""
GIVEN:
- Existing workflow
WHEN:
- API request to update an existing workflow with no triggers and actions
- API request to update an existing workflow with empty actions and no triggers
THEN:
- No changes are made to the workflow
- Actions are removed, but triggers are not
"""
response = self.client.patch(
f"{self.ENDPOINT}{self.workflow.id}/",
json.dumps(
{
"name": "Workflow Updated",
"order": 1,
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
workflow = Workflow.objects.get(id=self.workflow.id)
self.assertEqual(workflow.name, "Workflow Updated")
self.assertEqual(workflow.triggers.count(), 1)
self.assertEqual(workflow.actions.count(), 1)
response = self.client.patch(
f"{self.ENDPOINT}{self.workflow.id}/",
json.dumps(
{
"name": "Workflow Updated 2",
"order": 1,
"actions": [],
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
workflow = Workflow.objects.get(id=self.workflow.id)
self.assertEqual(workflow.name, "Workflow Updated 2")
self.assertEqual(workflow.triggers.count(), 1)
self.assertEqual(workflow.actions.count(), 0)
def test_api_auto_remove_orphaned_triggers_actions(self):
"""
GIVEN:
@ -630,3 +674,63 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, expected_resp_code)
def test_patch_trigger_cannot_change_id(self):
"""
GIVEN:
- An existing workflow trigger
- An existing workflow action
WHEN:
- PATCHing the trigger with a different 'id' in the body
- PATCHing the action with a different 'id' in the body
THEN:
- HTTP 400 error is returned
"""
response = self.client.patch(
f"/api/workflow_triggers/{self.trigger.id}/",
{
"id": self.trigger.id + 1,
"filter_filename": "patched.pdf",
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.trigger.refresh_from_db()
self.assertNotEqual(self.trigger.filter_filename, "patched.pdf")
response = self.client.patch(
f"/api/workflow_triggers/{self.trigger.id}/",
{
"id": self.trigger.id,
"filter_filename": "patched.pdf",
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.trigger.refresh_from_db()
self.assertEqual(self.trigger.filter_filename, "patched.pdf")
response = self.client.patch(
f"/api/workflow_actions/{self.action.id}/",
{
"id": self.action.id + 1,
"assign_title": "Patched Title",
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.action.refresh_from_db()
self.assertNotEqual(self.action.assign_title, "Patched Title")
response = self.client.patch(
f"/api/workflow_actions/{self.action.id}/",
{
"id": self.action.id,
"assign_title": "Patched Title",
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.action.refresh_from_db()
self.assertEqual(self.action.assign_title, "Patched Title")

View File

@ -1614,13 +1614,14 @@ class TestWorkflows(
def test_workflow_scheduled_trigger_negative_offset_customfield(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger and negative offset of -7 days (so 7 days after date)
- Custom field date initially set to 5 days ago trigger time = 2 days in future
- Then updated to 8 days ago trigger time = 1 day ago
- Workflow with offset -7 (i.e., 7 days *before* the date)
- doc1: value_date = 5 days ago trigger time = 12 days ago triggers
- doc2: value_date = 9 days in future trigger time = 2 days in future does NOT trigger
WHEN:
- Scheduled workflows are checked for document with custom field date 8 days in the past
- Scheduled workflows are checked
THEN:
- Workflow runs and document owner is updated
- doc1 has owner assigned
- doc2 remains untouched
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
@ -1640,38 +1641,49 @@ class TestWorkflows(
w.actions.add(action)
w.save()
doc = Document.objects.create(
title="sample test",
doc1 = Document.objects.create(
title="doc1",
correspondent=self.c,
original_filename="sample.pdf",
original_filename="doc1.pdf",
checksum="doc1-checksum",
)
cfi = CustomFieldInstance.objects.create(
document=doc,
CustomFieldInstance.objects.create(
document=doc1,
field=self.cf1,
value_date=timezone.now() - timedelta(days=5),
value_date=timezone.now().date() - timedelta(days=5),
)
doc2 = Document.objects.create(
title="doc2",
correspondent=self.c,
original_filename="doc2.pdf",
checksum="doc2-checksum",
)
CustomFieldInstance.objects.create(
document=doc2,
field=self.cf1,
value_date=timezone.now().date() + timedelta(days=9),
)
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertIsNone(doc.owner) # has not triggered yet
doc1.refresh_from_db()
self.assertEqual(doc1.owner, self.user2)
cfi.value_date = timezone.now() - timedelta(days=8)
cfi.save()
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
doc2.refresh_from_db()
self.assertIsNone(doc2.owner)
def test_workflow_scheduled_trigger_negative_offset_created(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger and negative offset of -7 days (so 7 days after date)
- Created date set to 8 days ago trigger time = 1 day ago and 5 days ago
- Existing workflow with SCHEDULED trigger and negative offset of -7 days (so 7 days before date)
- doc created 8 days ago trigger time = 15 days ago triggers
- doc2 created 8 days *in the future* trigger time = 1 day in future does NOT trigger
WHEN:
- Scheduled workflows are checked for document
THEN:
- Workflow runs and document owner is updated for the first document, not the second
- doc is matched and owner updated
- doc2 is untouched
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
@ -1703,7 +1715,7 @@ class TestWorkflows(
correspondent=self.c,
original_filename="sample2.pdf",
checksum="2",
created=timezone.now().date() - timedelta(days=5),
created=timezone.now().date() + timedelta(days=8),
)
tasks.check_scheduled_workflows()
@ -1712,6 +1724,40 @@ class TestWorkflows(
doc2.refresh_from_db()
self.assertIsNone(doc2.owner) # has not triggered yet
def test_offset_positive_means_after(self):
"""
GIVEN:
- Document created 30 days ago
- Workflow with offset +10
EXPECT:
- It triggers now, because created + 10 = 20 days ago < now
"""
doc = Document.objects.create(
title="Test doc",
created=timezone.now() - timedelta(days=30),
correspondent=self.c,
original_filename="test.pdf",
)
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_date_field=WorkflowTrigger.ScheduleDateField.CREATED,
schedule_offset_days=10,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
def test_workflow_scheduled_filters_queryset(self):
"""
GIVEN:

View File

@ -6,6 +6,7 @@ import re
import tempfile
import zipfile
from datetime import datetime
from distutils.util import strtobool
from pathlib import Path
from time import mktime
from unicodedata import normalize
@ -236,9 +237,15 @@ class PassUserMixin(GenericAPIView):
def get_serializer(self, *args, **kwargs):
kwargs.setdefault("user", self.request.user)
try:
full_perms = bool(
strtobool(str(self.request.query_params.get("full_perms", "false"))),
)
except ValueError:
full_perms = False
kwargs.setdefault(
"full_perms",
self.request.query_params.get("full_perms", False),
full_perms,
)
return super().get_serializer(*args, **kwargs)
@ -592,9 +599,15 @@ class DocumentViewSet(
kwargs.setdefault("context", self.get_serializer_context())
kwargs.setdefault("fields", fields)
kwargs.setdefault("truncate_content", truncate_content.lower() in ["true", "1"])
try:
full_perms = bool(
strtobool(str(self.request.query_params.get("full_perms", "false"))),
)
except ValueError:
full_perms = False
kwargs.setdefault(
"full_perms",
self.request.query_params.get("full_perms", False),
full_perms,
)
return super().get_serializer(*args, **kwargs)
@ -650,7 +663,7 @@ class DocumentViewSet(
)
def get_metadata(self, file, mime_type):
if not os.path.isfile(file):
if not Path(file).is_file():
return None
parser_class = get_parser_class_for_mime_type(mime_type)
@ -668,8 +681,8 @@ class DocumentViewSet(
return []
def get_filesize(self, filename):
if os.path.isfile(filename):
return os.stat(filename).st_size
if Path(filename).is_file():
return Path(filename).stat().st_size
else:
return None
@ -1215,31 +1228,37 @@ class UnifiedSearchViewSet(DocumentViewSet):
class LogViewSet(ViewSet):
permission_classes = (IsAuthenticated, PaperlessAdminPermissions)
log_files = ["paperless", "mail", "celery"]
ALLOWED_LOG_FILES = {
"paperless": "paperless.log",
"mail": "mail.log",
"celery": "celery.log",
}
def get_log_filename(self, log):
return os.path.join(settings.LOGGING_DIR, f"{log}.log")
def get_log_file(self, log_key: str) -> Path:
return Path(settings.LOGGING_DIR) / self.ALLOWED_LOG_FILES[log_key]
def retrieve(self, request, *args, **kwargs):
log_file = kwargs.get("pk")
if log_file not in self.log_files:
log_key = kwargs.get("pk")
if log_key not in self.ALLOWED_LOG_FILES:
raise Http404
filename = self.get_log_filename(log_file)
log_file = self.get_log_file(log_key)
if not os.path.isfile(filename):
if not log_file.is_file():
raise Http404
with open(filename) as f:
with log_file.open() as f:
lines = [line.rstrip() for line in f.readlines()]
return Response(lines)
def list(self, request, *args, **kwargs):
exist = [
log for log in self.log_files if os.path.isfile(self.get_log_filename(log))
existing_logs = [
log_key
for log_key in self.ALLOWED_LOG_FILES
if self.get_log_file(log_key).is_file()
]
return Response(exist)
return Response(existing_logs)
class SavedViewViewSet(ModelViewSet, PassUserMixin):
@ -2073,7 +2092,7 @@ class BulkDownloadView(GenericAPIView):
strategy.add_document(document)
# TODO(stumpylog): Investigate using FileResponse here
with open(temp.name, "rb") as f:
with Path(temp.name).open("rb") as f:
response = HttpResponse(f, content_type="application/zip")
response["Content-Disposition"] = '{}; filename="{}"'.format(
"attachment",
@ -2165,6 +2184,8 @@ class UiSettingsView(GenericAPIView):
general_config = GeneralConfig()
ui_settings["version"] = version.__full_version_str__
ui_settings["app_title"] = settings.APP_TITLE
if general_config.app_title is not None and len(general_config.app_title) > 0:
ui_settings["app_title"] = general_config.app_title
@ -2530,6 +2551,13 @@ class WorkflowTriggerViewSet(ModelViewSet):
queryset = WorkflowTrigger.objects.all()
def partial_update(self, request, *args, **kwargs):
if "id" in request.data and str(request.data["id"]) != str(kwargs["pk"]):
return HttpResponseBadRequest(
"ID in body does not match URL",
)
return super().partial_update(request, *args, **kwargs)
class WorkflowActionViewSet(ModelViewSet):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
@ -2548,6 +2576,13 @@ class WorkflowActionViewSet(ModelViewSet):
"assign_custom_fields",
)
def partial_update(self, request, *args, **kwargs):
if "id" in request.data and str(request.data["id"]) != str(kwargs["pk"]):
return HttpResponseBadRequest(
"ID in body does not match URL",
)
return super().partial_update(request, *args, **kwargs)
class WorkflowViewSet(ModelViewSet):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)

View File

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-14 19:08-0700\n"
"POT-Creation-Date: 2025-06-17 05:45+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@ -1185,12 +1185,12 @@ msgstr ""
msgid "Invalid color."
msgstr ""
#: documents/serialisers.py:1646
#: documents/serialisers.py:1645
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
#: documents/serialisers.py:1740
#: documents/serialisers.py:1739
msgid "Invalid variable detected."
msgstr ""

View File

@ -21,3 +21,10 @@ application = ProtocolTypeRouter(
"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
},
)
import logging # noqa: E402
from paperless.version import __full_version_str__ # noqa: E402
logger = logging.getLogger("paperless.asgi")
logger.info(f"[init] Paperless-ngx version: v{__full_version_str__}")

View File

@ -3,6 +3,7 @@ import os
import pwd
import shutil
import stat
from pathlib import Path
from django.conf import settings
from django.core.checks import Error
@ -19,26 +20,23 @@ writeable_hint = (
)
def path_check(var, directory):
messages = []
def path_check(var, directory: Path) -> list[Error]:
messages: list[Error] = []
if directory:
if not os.path.isdir(directory):
if not directory.is_dir():
messages.append(
Error(exists_message.format(var), exists_hint.format(directory)),
)
else:
test_file = os.path.join(
directory,
f"__paperless_write_test_{os.getpid()}__",
)
test_file: Path = directory / f"__paperless_write_test_{os.getpid()}__"
try:
with open(test_file, "w"):
with test_file.open("w"):
pass
except PermissionError:
dir_stat = os.stat(directory)
dir_mode = stat.filemode(dir_stat.st_mode)
dir_owner = pwd.getpwuid(dir_stat.st_uid).pw_name
dir_group = grp.getgrgid(dir_stat.st_gid).gr_name
dir_stat: os.stat_result = Path(directory).stat()
dir_mode: str = stat.filemode(dir_stat.st_mode)
dir_owner: str = pwd.getpwuid(dir_stat.st_uid).pw_name
dir_group: str = grp.getgrgid(dir_stat.st_gid).gr_name
messages.append(
Error(
writeable_message.format(var),
@ -48,14 +46,18 @@ def path_check(var, directory):
),
)
finally:
if os.path.isfile(test_file):
os.remove(test_file)
try:
if test_file.is_file():
test_file.unlink()
except (PermissionError, OSError):
# Skip cleanup if we can't access the file — expected in permission tests
pass
return messages
@register()
def paths_check(app_configs, **kwargs):
def paths_check(app_configs, **kwargs) -> list[Error]:
"""
Check the various paths for existence, readability and writeability
"""

View File

@ -27,9 +27,9 @@ class TestChecks(DirectoriesMixin, TestCase):
self.assertEqual(paths_check(None), [])
@override_settings(
MEDIA_ROOT="uuh",
DATA_DIR="whatever",
CONSUMPTION_DIR="idontcare",
MEDIA_ROOT=Path("uuh"),
DATA_DIR=Path("whatever"),
CONSUMPTION_DIR=Path("idontcare"),
)
def test_paths_check_dont_exist(self):
msgs = paths_check(None)

View File

@ -134,6 +134,13 @@ class UserViewSet(ModelViewSet):
)
return super().update(request, *args, **kwargs)
@extend_schema(
request=None,
responses={
200: OpenApiTypes.BOOL,
404: OpenApiTypes.STR,
},
)
@action(detail=True, methods=["post"])
def deactivate_totp(self, request, pk=None):
request_user = request.user
@ -342,6 +349,10 @@ class ApplicationConfigurationViewSet(ModelViewSet):
serializer_class = ApplicationConfigurationSerializer
permission_classes = (IsAuthenticated, DjangoModelPermissions)
@extend_schema(exclude=True)
def create(self, request, *args, **kwargs):
return Response(status=405) # Not Allowed
@extend_schema_view(
post=extend_schema(

View File

@ -14,3 +14,10 @@ from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "paperless.settings")
application = get_wsgi_application()
import logging # noqa: E402
from paperless.version import __full_version_str__ # noqa: E402
logger = logging.getLogger("paperless.wsgi")
logger.info(f"[init] Paperless-ngx version: v{__full_version_str__}")

View File

@ -1,7 +1,6 @@
import datetime
import itertools
import logging
import os
import ssl
import tempfile
import traceback
@ -484,7 +483,7 @@ class MailAccountHandler(LoggingMixin):
return message.subject
elif rule.assign_title_from == MailRule.TitleSource.FROM_FILENAME:
return os.path.splitext(os.path.basename(att.filename))[0]
return Path(att.filename).stem
elif rule.assign_title_from == MailRule.TitleSource.NONE:
return None
@ -908,7 +907,7 @@ class MailAccountHandler(LoggingMixin):
dir=settings.SCRATCH_DIR,
suffix=".eml",
)
with open(temp_filename, "wb") as f:
with Path(temp_filename).open("wb") as f:
# Move "From"-header to beginning of file
# TODO: This ugly workaround is needed because the parser is
# chosen only by the mime_type detected via magic

View File

@ -108,18 +108,20 @@ class MailRuleSerializer(OwnedObjectSerializer):
return instance
def create(self, validated_data):
if "assign_tags" in validated_data:
assign_tags = validated_data.pop("assign_tags")
assign_tags = validated_data.pop("assign_tags", [])
mail_rule = super().create(validated_data)
if assign_tags:
mail_rule.assign_tags.set(assign_tags)
return mail_rule
def validate(self, attrs):
action = attrs.get("action")
action_parameter = attrs.get("action_parameter")
if (
attrs["action"] == MailRule.MailAction.TAG
or attrs["action"] == MailRule.MailAction.MOVE
) and attrs["action_parameter"] is None:
action in [MailRule.MailAction.TAG, MailRule.MailAction.MOVE]
and not action_parameter
):
raise serializers.ValidationError("An action parameter is required.")
return attrs

View File

@ -1822,3 +1822,66 @@ class TestMailAccountProcess(APITestCase):
response = self.client.post(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
m.assert_called_once()
class TestMailRuleAPI(APITestCase):
def setUp(self):
self.user = User.objects.create_superuser(
username="testuser",
password="testpassword",
)
self.client.force_authenticate(user=self.user)
self.account = MailAccount.objects.create(
imap_server="imap.example.com",
imap_port=993,
imap_security=MailAccount.ImapSecurity.SSL,
username="admin",
password="secret",
account_type=MailAccount.MailAccountType.IMAP,
owner=self.user,
)
self.url = "/api/mail_rules/"
def test_create_mail_rule(self):
"""
GIVEN:
- Valid data for creating a mail rule
WHEN:
- A POST request is made to the mail rules endpoint
THEN:
- The rule should be created successfully
- The response should contain the created rule's details
"""
data = {
"name": "Test Rule",
"account": self.account.pk,
"action": MailRule.MailAction.MOVE,
"action_parameter": "inbox",
}
response = self.client.post(self.url, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(MailRule.objects.count(), 1)
rule = MailRule.objects.first()
self.assertEqual(rule.name, "Test Rule")
def test_mail_rule_action_parameter_required_for_tag_or_move(self):
"""
GIVEN:
- Valid data for creating a mail rule without action_parameter
WHEN:
- A POST request is made to the mail rules endpoint
THEN:
- The request should fail with a 400 Bad Request status
- The response should indicate that action_parameter is required
"""
data = {
"name": "Test Rule",
"account": self.account.pk,
"action": MailRule.MailAction.MOVE,
}
response = self.client.post(self.url, data, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(
"action parameter is required",
str(response.data["non_field_errors"]),
)

View File

@ -1,4 +1,3 @@
import os
import shutil
import tempfile
import uuid
@ -70,13 +69,13 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(uuid.uuid4())
page_count = parser.get_page_count(
os.path.join(self.SAMPLE_FILES, "simple-digital.pdf"),
(self.SAMPLE_FILES / "simple-digital.pdf").as_posix(),
"application/pdf",
)
self.assertEqual(page_count, 1)
page_count = parser.get_page_count(
os.path.join(self.SAMPLE_FILES, "multi-page-mixed.pdf"),
(self.SAMPLE_FILES / "multi-page-mixed.pdf").as_posix(),
"application/pdf",
)
self.assertEqual(page_count, 6)
@ -93,7 +92,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(uuid.uuid4())
with self.assertLogs("paperless.parsing.tesseract", level="WARNING") as cm:
page_count = parser.get_page_count(
os.path.join(self.SAMPLE_FILES, "password-protected.pdf"),
(self.SAMPLE_FILES / "password-protected.pdf").as_posix(),
"application/pdf",
)
self.assertEqual(page_count, None)
@ -102,7 +101,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_thumbnail(self):
parser = RasterisedDocumentParser(uuid.uuid4())
thumb = parser.get_thumbnail(
os.path.join(self.SAMPLE_FILES, "simple-digital.pdf"),
(self.SAMPLE_FILES / "simple-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(thumb)
@ -119,7 +118,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(uuid.uuid4())
thumb = parser.get_thumbnail(
os.path.join(self.SAMPLE_FILES, "simple-digital.pdf"),
(self.SAMPLE_FILES / "simple-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(thumb)
@ -127,7 +126,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_thumbnail_encrypted(self):
parser = RasterisedDocumentParser(uuid.uuid4())
thumb = parser.get_thumbnail(
os.path.join(self.SAMPLE_FILES, "encrypted.pdf"),
(self.SAMPLE_FILES / "encrypted.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(thumb)
@ -135,17 +134,17 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_get_dpi(self):
parser = RasterisedDocumentParser(None)
dpi = parser.get_dpi(os.path.join(self.SAMPLE_FILES, "simple-no-dpi.png"))
dpi = parser.get_dpi((self.SAMPLE_FILES / "simple-no-dpi.png").as_posix())
self.assertEqual(dpi, None)
dpi = parser.get_dpi(os.path.join(self.SAMPLE_FILES, "simple.png"))
dpi = parser.get_dpi((self.SAMPLE_FILES / "simple.png").as_posix())
self.assertEqual(dpi, 72)
def test_simple_digital(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "simple-digital.pdf"),
(self.SAMPLE_FILES / "simple-digital.pdf").as_posix(),
"application/pdf",
)
@ -157,7 +156,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "with-form.pdf"),
(self.SAMPLE_FILES / "with-form.pdf").as_posix(),
"application/pdf",
)
@ -173,7 +172,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "with-form.pdf"),
(self.SAMPLE_FILES / "with-form.pdf").as_posix(),
"application/pdf",
)
@ -187,7 +186,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_signed(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "signed.pdf"), "application/pdf")
parser.parse((self.SAMPLE_FILES / "signed.pdf").as_posix(), "application/pdf")
self.assertIsNone(parser.archive_path)
self.assertContainsStrings(
@ -203,7 +202,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "encrypted.pdf"),
(self.SAMPLE_FILES / "encrypted.pdf").as_posix(),
"application/pdf",
)
@ -214,7 +213,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_with_form_error_notext(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "with-form.pdf"),
(self.SAMPLE_FILES / "with-form.pdf").as_posix(),
"application/pdf",
)
@ -228,7 +227,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "with-form.pdf"),
(self.SAMPLE_FILES / "with-form.pdf").as_posix(),
"application/pdf",
)
@ -240,7 +239,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_image_simple(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple.png"), "image/png")
parser.parse((self.SAMPLE_FILES / "simple.png").as_posix(), "image/png")
self.assertIsFile(parser.archive_path)
@ -252,11 +251,11 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
with tempfile.TemporaryDirectory() as tempdir:
# Copy sample file to temp directory, as the parsing changes the file
# and this makes it modified to Git
sample_file = os.path.join(self.SAMPLE_FILES, "simple-alpha.png")
dest_file = os.path.join(tempdir, "simple-alpha.png")
sample_file = self.SAMPLE_FILES / "simple-alpha.png"
dest_file = Path(tempdir) / "simple-alpha.png"
shutil.copy(sample_file, dest_file)
parser.parse(dest_file, "image/png")
parser.parse(dest_file.as_posix(), "image/png")
self.assertIsFile(parser.archive_path)
@ -266,7 +265,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(None)
dpi = parser.calculate_a4_dpi(
os.path.join(self.SAMPLE_FILES, "simple-no-dpi.png"),
(self.SAMPLE_FILES / "simple-no-dpi.png").as_posix(),
)
self.assertEqual(dpi, 62)
@ -278,7 +277,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def f():
parser.parse(
os.path.join(self.SAMPLE_FILES, "simple-no-dpi.png"),
(self.SAMPLE_FILES / "simple-no-dpi.png").as_posix(),
"image/png",
)
@ -288,7 +287,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_image_no_dpi_default(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple-no-dpi.png"), "image/png")
parser.parse((self.SAMPLE_FILES / "simple-no-dpi.png").as_posix(), "image/png")
self.assertIsFile(parser.archive_path)
@ -300,7 +299,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_multi_page(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -313,7 +312,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_multi_page_pages_skip(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -326,7 +325,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_multi_page_pages_redo(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -339,7 +338,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_multi_page_pages_force(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -352,7 +351,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def test_multi_page_analog_pages_skip(self):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -376,7 +375,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -398,7 +397,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
self.assertIsFile(parser.archive_path)
@ -420,7 +419,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsNone(parser.archive_path)
@ -443,7 +442,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
@ -468,7 +467,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsNotNone(parser.archive_path)
@ -491,7 +490,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
self.assertIsNotNone(parser.archive_path)
@ -514,7 +513,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsNone(parser.archive_path)
@ -537,7 +536,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
self.assertIsNotNone(parser.archive_path)
@ -560,7 +559,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-digital.pdf"),
(self.SAMPLE_FILES / "multi-page-digital.pdf").as_posix(),
"application/pdf",
)
self.assertIsNone(parser.archive_path)
@ -583,7 +582,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"),
(self.SAMPLE_FILES / "multi-page-images.pdf").as_posix(),
"application/pdf",
)
self.assertIsNone(parser.archive_path)
@ -606,7 +605,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-mixed.pdf"),
(self.SAMPLE_FILES / "multi-page-mixed.pdf").as_posix(),
"application/pdf",
)
self.assertIsNotNone(parser.archive_path)
@ -616,7 +615,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
["page 1", "page 2", "page 3", "page 4", "page 5", "page 6"],
)
with open(os.path.join(parser.tempdir, "sidecar.txt")) as f:
with (parser.tempdir / "sidecar.txt").open() as f:
sidecar = f.read()
self.assertIn("[OCR skipped on page(s) 4-6]", sidecar)
@ -637,7 +636,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "single-page-mixed.pdf"),
(self.SAMPLE_FILES / "single-page-mixed.pdf").as_posix(),
"application/pdf",
)
self.assertIsNotNone(parser.archive_path)
@ -651,7 +650,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
],
)
with open(os.path.join(parser.tempdir, "sidecar.txt")) as f:
with (parser.tempdir / "sidecar.txt").open() as f:
sidecar = f.read().lower()
self.assertIn("this is some text, but in an image, also on page 1.", sidecar)
@ -674,7 +673,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-mixed.pdf"),
(self.SAMPLE_FILES / "multi-page-mixed.pdf").as_posix(),
"application/pdf",
)
self.assertIsNone(parser.archive_path)
@ -686,7 +685,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
@override_settings(OCR_MODE="skip", OCR_ROTATE_PAGES=True)
def test_rotate(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "rotated.pdf"), "application/pdf")
parser.parse((self.SAMPLE_FILES / "rotated.pdf").as_posix(), "application/pdf")
self.assertContainsStrings(
parser.get_text(),
[
@ -708,7 +707,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "multi-page-images.tiff"),
(self.SAMPLE_FILES / "multi-page-images.tiff").as_posix(),
"image/tiff",
)
self.assertIsFile(parser.archive_path)
@ -728,7 +727,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
- Text from all pages extracted
"""
parser = RasterisedDocumentParser(None)
sample_file = os.path.join(self.SAMPLE_FILES, "multi-page-images-alpha.tiff")
sample_file = self.SAMPLE_FILES / "multi-page-images-alpha.tiff"
with tempfile.NamedTemporaryFile() as tmp_file:
shutil.copy(sample_file, tmp_file.name)
parser.parse(
@ -753,10 +752,9 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
- Text from all pages extracted
"""
parser = RasterisedDocumentParser(None)
sample_file = os.path.join(
self.SAMPLE_FILES,
"multi-page-images-alpha-rgb.tiff",
)
sample_file = (
self.SAMPLE_FILES / "multi-page-images-alpha-rgb.tiff"
).as_posix()
with tempfile.NamedTemporaryFile() as tmp_file:
shutil.copy(sample_file, tmp_file.name)
parser.parse(
@ -845,7 +843,7 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
parser = RasterisedDocumentParser(None)
parser.parse(
os.path.join(self.SAMPLE_FILES, "rtl-test.pdf"),
(self.SAMPLE_FILES / "rtl-test.pdf").as_posix(),
"application/pdf",
)
@ -860,49 +858,52 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertRaises(
ParseError,
parser.parse,
os.path.join(self.SAMPLE_FILES, "simple-digital.pdf"),
(self.SAMPLE_FILES / "simple-digital.pdf").as_posix(),
"application/pdf",
)
class TestParserFileTypes(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
SAMPLE_FILES = os.path.join(os.path.dirname(__file__), "samples")
SAMPLE_FILES = Path(__file__).parent / "samples"
def test_bmp(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple.bmp"), "image/bmp")
parser.parse((self.SAMPLE_FILES / "simple.bmp").as_posix(), "image/bmp")
self.assertIsFile(parser.archive_path)
self.assertIn("this is a test document", parser.get_text().lower())
def test_jpg(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple.jpg"), "image/jpeg")
parser.parse((self.SAMPLE_FILES / "simple.jpg").as_posix(), "image/jpeg")
self.assertIsFile(parser.archive_path)
self.assertIn("this is a test document", parser.get_text().lower())
def test_heic(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple.heic"), "image/heic")
parser.parse((self.SAMPLE_FILES / "simple.heic").as_posix(), "image/heic")
self.assertIsFile(parser.archive_path)
self.assertIn("pizza", parser.get_text().lower())
@override_settings(OCR_IMAGE_DPI=200)
def test_gif(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple.gif"), "image/gif")
parser.parse((self.SAMPLE_FILES / "simple.gif").as_posix(), "image/gif")
self.assertIsFile(parser.archive_path)
self.assertIn("this is a test document", parser.get_text().lower())
def test_tiff(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple.tif"), "image/tiff")
parser.parse((self.SAMPLE_FILES / "simple.tif").as_posix(), "image/tiff")
self.assertIsFile(parser.archive_path)
self.assertIn("this is a test document", parser.get_text().lower())
@override_settings(OCR_IMAGE_DPI=72)
def test_webp(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "document.webp"), "image/webp")
parser.parse(
(self.SAMPLE_FILES / "document.webp").as_posix(),
"image/webp",
)
self.assertIsFile(parser.archive_path)
# Older tesseracts consistently mangle the space between "a webp",
# tesseract 5.3.0 seems to do a better job, so we're accepting both