Compare commits

..
Author SHA1 Message Date
shamoon b5d44f6224 Remove angular platform-browser-dynamic 2026-07-10 01:00:47 -07:00
153 changed files with 49192 additions and 86532 deletions
+4 -4
View File
@@ -38,7 +38,7 @@ repos:
- json - json
# See https://github.com/prettier/prettier/issues/15742 for the fork reason # See https://github.com/prettier/prettier/issues/15742 for the fork reason
- repo: https://github.com/rbubley/mirrors-prettier - repo: https://github.com/rbubley/mirrors-prettier
rev: 'v3.9.4' rev: 'v3.8.4'
hooks: hooks:
- id: prettier - id: prettier
types_or: types_or:
@@ -46,16 +46,16 @@ repos:
- ts - ts
- markdown - markdown
additional_dependencies: additional_dependencies:
- prettier@3.9.4 - prettier@3.8.3
- 'prettier-plugin-organize-imports@4.3.0' - 'prettier-plugin-organize-imports@4.3.0'
# Python hooks # Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.20 rev: v0.15.17
hooks: hooks:
- id: ruff-check - id: ruff-check
- id: ruff-format - id: ruff-format
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: "v2.25.1" rev: "v2.24.1"
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt
additional_dependencies: [tomli] additional_dependencies: [tomli]
+1 -5
View File
@@ -30,7 +30,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs # Purpose: Installs s6-overlay and rootfs
# Comments: # Comments:
# - Don't leave anything extra in here either # - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.11.28-python3.12-trixie-slim AS s6-overlay-base FROM ghcr.io/astral-sh/uv:0.11.19-python3.12-trixie-slim AS s6-overlay-base
WORKDIR /usr/src/s6 WORKDIR /usr/src/s6
@@ -238,10 +238,6 @@ RUN set -eux \
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \ && chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \
&& echo "Making fontconfig cache writable for arbitrary container UIDs" \ && echo "Making fontconfig cache writable for arbitrary container UIDs" \
&& chmod 1777 /var/cache/fontconfig \ && chmod 1777 /var/cache/fontconfig \
&& echo "Making /run world-writable for rootless operation" \
&& chmod 1777 /run \
&& echo "Removing setuid from s6-overlay-suexec for rootless compat" \
&& chmod u-s /command/s6-overlay-suexec \
&& echo "Collecting static files" \ && echo "Collecting static files" \
&& PAPERLESS_SECRET_KEY=build-time-dummy s6-setuidgid paperless python3 manage.py collectstatic --clear --no-input --link \ && PAPERLESS_SECRET_KEY=build-time-dummy s6-setuidgid paperless python3 manage.py collectstatic --clear --no-input --link \
&& PAPERLESS_SECRET_KEY=build-time-dummy s6-setuidgid paperless python3 manage.py compilemessages \ && PAPERLESS_SECRET_KEY=build-time-dummy s6-setuidgid paperless python3 manage.py compilemessages \
+3 -3
View File
@@ -4,7 +4,7 @@
# correct networking for the tests # correct networking for the tests
services: services:
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.34 image: docker.io/gotenberg/gotenberg:8.33
hostname: gotenberg hostname: gotenberg
container_name: gotenberg container_name: gotenberg
network_mode: host network_mode: host
@@ -24,7 +24,7 @@ services:
network_mode: host network_mode: host
restart: unless-stopped restart: unless-stopped
greenmail: greenmail:
image: docker.io/greenmail/standalone:2.1.9 image: docker.io/greenmail/standalone:2.1.8
hostname: greenmail hostname: greenmail
container_name: greenmail container_name: greenmail
environment: environment:
@@ -35,7 +35,7 @@ services:
- "3143:3143" # IMAP - "3143:3143" # IMAP
restart: unless-stopped restart: unless-stopped
nginx: nginx:
image: docker.io/nginx:1.31.2-alpine image: docker.io/nginx:1.31.1-alpine
hostname: nginx hostname: nginx
container_name: nginx container_name: nginx
ports: ports:
@@ -72,7 +72,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.34 image: docker.io/gotenberg/gotenberg:8.33
restart: unless-stopped restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not # The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript. # want to allow external content like tracking pixels or even javascript.
@@ -67,7 +67,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.34 image: docker.io/gotenberg/gotenberg:8.33
restart: unless-stopped restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not # The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript. # want to allow external content like tracking pixels or even javascript.
@@ -56,7 +56,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.34 image: docker.io/gotenberg/gotenberg:8.33
restart: unless-stopped restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not # The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript. # want to allow external content like tracking pixels or even javascript.
-143
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -326,11 +326,3 @@ behind a reverse proxy may need to set
[`PAPERLESS_TRUSTED_PROXIES`](configuration.md#PAPERLESS_TRUSTED_PROXIES), [`PAPERLESS_TRUSTED_PROXIES`](configuration.md#PAPERLESS_TRUSTED_PROXIES),
[`PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER`](configuration.md#PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER), [`PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER`](configuration.md#PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER),
or both, to avoid `403 Forbidden` errors on login. or both, to avoid `403 Forbidden` errors on login.
## Database Migrations
Some integer fields have been changed to smaller types to reduce database size. If you have any `MailRule` records with a `maximum_age` greater than 32767, they will be clamped to 32767 during the migration to avoid errors during migration.
### Action Required
No user action is required. The migration will automatically clamp any `MailRule.maximum_age` values greater than 32767 to 32767 during the migration process.
+1 -1
View File
@@ -142,7 +142,7 @@ a [superuser](usage.md#superusers) account.
It is not possible to run the container rootless if additional languages are specified via `PAPERLESS_OCR_LANGUAGES`. It is not possible to run the container rootless if additional languages are specified via `PAPERLESS_OCR_LANGUAGES`.
If you want to run Paperless as a rootless container, set `user:` in `docker-compose.yml` to the UID and GID of your host user (use `id -u` and `id -g` to find these values). The container process starts directly as that user with no internal privilege remapping. Any UID and GID is supported: If you want to run Paperless as a rootless container, set `user:` in `docker-compose.yml` to the UID and GID of your host user (use `id -u` and `id -g` to find these values). The container process starts directly as that user with no internal privilege remapping:
```yaml ```yaml
webserver: webserver:
+4 -4
View File
@@ -16,7 +16,7 @@ classifiers = [
dependencies = [ dependencies = [
"azure-ai-documentintelligence>=1.0.2", "azure-ai-documentintelligence>=1.0.2",
"babel>=2.17", "babel>=2.17",
"bleach~=6.4.0", "bleach~=6.3.0",
"celery[redis]~=5.6.2", "celery[redis]~=5.6.2",
"channels~=4.2", "channels~=4.2",
"channels-redis~=4.2", "channels-redis~=4.2",
@@ -56,7 +56,7 @@ dependencies = [
"llama-index-embeddings-openai-like>=0.2.2", "llama-index-embeddings-openai-like>=0.2.2",
"llama-index-llms-ollama>=0.9.1", "llama-index-llms-ollama>=0.9.1",
"llama-index-llms-openai-like>=0.7.1", "llama-index-llms-openai-like>=0.7.1",
"nltk~=3.10.0", "nltk~=3.9.1",
"ocrmypdf~=17.4.2", "ocrmypdf~=17.4.2",
"openai>=2.32", "openai>=2.32",
"pathvalidate~=3.3.1", "pathvalidate~=3.3.1",
@@ -101,11 +101,11 @@ dev = [
{ include-group = "testing" }, { include-group = "testing" },
] ]
docs = [ docs = [
"zensical>=0.0.47", "zensical>=0.0.43",
] ]
lint = [ lint = [
"prek~=0.3.10", "prek~=0.3.10",
"ruff~=0.15.20", "ruff~=0.15.15",
] ]
testing = [ testing = [
"daphne", "daphne",
-10
View File
@@ -79,16 +79,6 @@
"glob": "{pdf.worker.min.mjs,pdf.min.mjs}", "glob": "{pdf.worker.min.mjs,pdf.min.mjs}",
"input": "node_modules/pdfjs-dist/legacy/build/", "input": "node_modules/pdfjs-dist/legacy/build/",
"output": "/assets/js/" "output": "/assets/js/"
},
{
"glob": "**/*",
"input": "node_modules/pdfjs-dist/wasm/",
"output": "/assets/wasm/"
},
{
"glob": "**/*",
"input": "node_modules/pdfjs-dist/iccs/",
"output": "/assets/iccs/"
} }
], ],
"styles": [ "styles": [
+122 -151
View File
@@ -5,14 +5,14 @@
<trans-unit id="ngb.alert.close" datatype="html"> <trans-unit id="ngb.alert.close" datatype="html">
<source>Close</source> <source>Close</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/alert/alert.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/alert/alert.ts</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">39</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.carousel.slide-number" datatype="html"> <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> <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-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/carousel/carousel.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">129,133</context> <context context-type="linenumber">129,133</context>
</context-group> </context-group>
<note priority="1" from="description">Currently selected slide number read by screen reader</note> <note priority="1" from="description">Currently selected slide number read by screen reader</note>
@@ -20,114 +20,114 @@
<trans-unit id="ngb.carousel.previous" datatype="html"> <trans-unit id="ngb.carousel.previous" datatype="html">
<source>Previous</source> <source>Previous</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/carousel/carousel.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">160,161</context> <context context-type="linenumber">160,161</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.carousel.next" datatype="html"> <trans-unit id="ngb.carousel.next" datatype="html">
<source>Next</source> <source>Next</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/carousel/carousel.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">205,206</context> <context context-type="linenumber">205,206</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.datepicker.previous-month" datatype="html"> <trans-unit id="ngb.datepicker.previous-month" datatype="html">
<source>Previous month</source> <source>Previous month</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/datepicker-navigation.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">74,76</context> <context context-type="linenumber">74,76</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/datepicker-navigation.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">103</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.datepicker.next-month" datatype="html"> <trans-unit id="ngb.datepicker.next-month" datatype="html">
<source>Next month</source> <source>Next month</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/datepicker-navigation.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">103</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/datepicker-navigation.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">103</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.datepicker.select-month" datatype="html"> <trans-unit id="ngb.datepicker.select-month" datatype="html">
<source>Select month</source> <source>Select month</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/ngb-calendar.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/ngb-calendar.ts</context>
<context context-type="linenumber">63,65</context> <context context-type="linenumber">63,65</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/ngb-calendar.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/ngb-calendar.ts</context>
<context context-type="linenumber">106,107</context> <context context-type="linenumber">106,107</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.datepicker.select-year" datatype="html"> <trans-unit id="ngb.datepicker.select-year" datatype="html">
<source>Select year</source> <source>Select year</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/ngb-calendar.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/ngb-calendar.ts</context>
<context context-type="linenumber">145,146</context> <context context-type="linenumber">145,146</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/datepicker/ngb-calendar.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/datepicker/ngb-calendar.ts</context>
<context context-type="linenumber">179,183</context> <context context-type="linenumber">179,183</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.first" datatype="html"> <trans-unit id="ngb.pagination.first" datatype="html">
<source>««</source> <source>««</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">302</context> <context context-type="linenumber">302</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.previous" datatype="html"> <trans-unit id="ngb.pagination.previous" datatype="html">
<source>«</source> <source>«</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">333,335</context> <context context-type="linenumber">333,335</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.next" datatype="html"> <trans-unit id="ngb.pagination.next" datatype="html">
<source>»</source> <source>»</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">375</context> <context context-type="linenumber">375</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.last" datatype="html"> <trans-unit id="ngb.pagination.last" datatype="html">
<source>»»</source> <source>»»</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">408</context> <context context-type="linenumber">408</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.first-aria" datatype="html"> <trans-unit id="ngb.pagination.first-aria" datatype="html">
<source>First</source> <source>First</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">437</context> <context context-type="linenumber">437</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.previous-aria" datatype="html"> <trans-unit id="ngb.pagination.previous-aria" datatype="html">
<source>Previous</source> <source>Previous</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">472,474</context> <context context-type="linenumber">472,474</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.next-aria" datatype="html"> <trans-unit id="ngb.pagination.next-aria" datatype="html">
<source>Next</source> <source>Next</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">506</context> <context context-type="linenumber">506</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.pagination.last-aria" datatype="html"> <trans-unit id="ngb.pagination.last-aria" datatype="html">
<source>Last</source> <source>Last</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/pagination/pagination.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/pagination/pagination.ts</context>
<context context-type="linenumber">506</context> <context context-type="linenumber">506</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
@@ -135,105 +135,105 @@
<source><x id="INTERPOLATION" equiv-text="barConfig); <source><x id="INTERPOLATION" equiv-text="barConfig);
pu"/></source> pu"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/progressbar/progressbar.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/progressbar/progressbar.ts</context>
<context context-type="linenumber">40,41</context> <context context-type="linenumber">40,41</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.HH" datatype="html"> <trans-unit id="ngb.timepicker.HH" datatype="html">
<source>HH</source> <source>HH</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">263,264</context> <context context-type="linenumber">263,264</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.hours" datatype="html"> <trans-unit id="ngb.timepicker.hours" datatype="html">
<source>Hours</source> <source>Hours</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">301,302</context> <context context-type="linenumber">301,302</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.MM" datatype="html"> <trans-unit id="ngb.timepicker.MM" datatype="html">
<source>MM</source> <source>MM</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">341,342</context> <context context-type="linenumber">341,342</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.minutes" datatype="html"> <trans-unit id="ngb.timepicker.minutes" datatype="html">
<source>Minutes</source> <source>Minutes</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">381,384</context> <context context-type="linenumber">381,384</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.increment-hours" datatype="html"> <trans-unit id="ngb.timepicker.increment-hours" datatype="html">
<source>Increment hours</source> <source>Increment hours</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">429,430</context> <context context-type="linenumber">429,430</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.decrement-hours" datatype="html"> <trans-unit id="ngb.timepicker.decrement-hours" datatype="html">
<source>Decrement hours</source> <source>Decrement hours</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.increment-minutes" datatype="html"> <trans-unit id="ngb.timepicker.increment-minutes" datatype="html">
<source>Increment minutes</source> <source>Increment minutes</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.decrement-minutes" datatype="html"> <trans-unit id="ngb.timepicker.decrement-minutes" datatype="html">
<source>Decrement minutes</source> <source>Decrement minutes</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.SS" datatype="html"> <trans-unit id="ngb.timepicker.SS" datatype="html">
<source>SS</source> <source>SS</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.seconds" datatype="html"> <trans-unit id="ngb.timepicker.seconds" datatype="html">
<source>Seconds</source> <source>Seconds</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.increment-seconds" datatype="html"> <trans-unit id="ngb.timepicker.increment-seconds" datatype="html">
<source>Increment seconds</source> <source>Increment seconds</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.decrement-seconds" datatype="html"> <trans-unit id="ngb.timepicker.decrement-seconds" datatype="html">
<source>Decrement seconds</source> <source>Decrement seconds</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.timepicker.PM" datatype="html"> <trans-unit id="ngb.timepicker.PM" datatype="html">
<source><x id="INTERPOLATION"/></source> <source><x id="INTERPOLATION"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/timepicker/timepicker.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/timepicker/timepicker.ts</context>
<context context-type="linenumber">442</context> <context context-type="linenumber">442</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ngb.toast.close-aria" datatype="html"> <trans-unit id="ngb.toast.close-aria" datatype="html">
<source>Close</source> <source>Close</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_88eda8c8846cc5cfe00e25de83b3f075/node_modules/src/toast/toast.ts</context> <context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@21.0.0_@angular+common@22.0.5_@angular+core@22.0.5_@angular+_d3d748c9cb3098b2696b844e4082b488/node_modules/src/toast/toast.ts</context>
<context context-type="linenumber">116</context> <context context-type="linenumber">116</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
@@ -332,7 +332,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">86</context> <context context-type="linenumber">85</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
@@ -379,11 +379,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">273</context> <context context-type="linenumber">272</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">275</context> <context context-type="linenumber">274</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5890330709052835856" datatype="html"> <trans-unit id="5890330709052835856" datatype="html">
@@ -724,11 +724,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">308</context> <context context-type="linenumber">307</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">311</context> <context context-type="linenumber">310</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2272120016352772836" datatype="html"> <trans-unit id="2272120016352772836" datatype="html">
@@ -757,14 +757,14 @@
<source>lines</source> <source>lines</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">18</context> <context context-type="linenumber">17</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8838884664569764142" datatype="html"> <trans-unit id="8838884664569764142" datatype="html">
<source>Auto refresh</source> <source>Auto refresh</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">22</context> <context context-type="linenumber">21</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
@@ -775,11 +775,11 @@
<source>Loading...</source> <source>Loading...</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">38</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">49</context> <context context-type="linenumber">48</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
@@ -886,7 +886,7 @@
<source>Jump to bottom</source> <source>Jump to bottom</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">63</context> <context context-type="linenumber">62</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1255048712725285892" datatype="html"> <trans-unit id="1255048712725285892" datatype="html">
@@ -1135,11 +1135,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">233</context> <context context-type="linenumber">232</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">235</context> <context context-type="linenumber">234</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
@@ -1696,7 +1696,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">204</context> <context context-type="linenumber">203</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context> <context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context>
@@ -1801,11 +1801,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">296</context> <context context-type="linenumber">295</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">298</context> <context context-type="linenumber">297</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8492095365580052820" datatype="html"> <trans-unit id="8492095365580052820" datatype="html">
@@ -1905,18 +1905,11 @@
<context context-type="linenumber">227</context> <context context-type="linenumber">227</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3570013039254960445" datatype="html">
<source>Search tasks</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="6849725902312323996" datatype="html"> <trans-unit id="6849725902312323996" datatype="html">
<source>Reset filters</source> <source>Reset filters</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">105</context> <context context-type="linenumber">104</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@@ -1931,14 +1924,14 @@
<source>{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</source> <source>{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">126</context> <context context-type="linenumber">125</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8953033926734869941" datatype="html"> <trans-unit id="8953033926734869941" datatype="html">
<source>Name</source> <source>Name</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">148</context> <context context-type="linenumber">147</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
@@ -2049,7 +2042,7 @@
<source>Created</source> <source>Created</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">149</context> <context context-type="linenumber">148</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
@@ -2080,21 +2073,21 @@
<source>Results</source> <source>Results</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">151</context> <context context-type="linenumber">150</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="314315645942131479" datatype="html"> <trans-unit id="314315645942131479" datatype="html">
<source>Info</source> <source>Info</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">153</context> <context context-type="linenumber">152</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3193976279273491157" datatype="html"> <trans-unit id="3193976279273491157" datatype="html">
<source>Actions</source> <source>Actions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">154</context> <context context-type="linenumber">153</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context> <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
@@ -2165,14 +2158,14 @@
<source>click for full output</source> <source>click for full output</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">205</context> <context context-type="linenumber">204</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1536087519743707362" datatype="html"> <trans-unit id="1536087519743707362" datatype="html">
<source>Dismiss</source> <source>Dismiss</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">218</context> <context context-type="linenumber">217</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
@@ -2188,35 +2181,35 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">51</context> <context context-type="linenumber">50</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2134950584701094962" datatype="html"> <trans-unit id="2134950584701094962" datatype="html">
<source>Open Document</source> <source>Open Document</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">223</context> <context context-type="linenumber">222</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5404759957685833020" datatype="html"> <trans-unit id="5404759957685833020" datatype="html">
<source>Result message</source> <source>Result message</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">236</context> <context context-type="linenumber">235</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6621329748219109148" datatype="html"> <trans-unit id="6621329748219109148" datatype="html">
<source>Duplicate</source> <source>Duplicate</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">243</context> <context context-type="linenumber">242</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7593555694782789615" datatype="html"> <trans-unit id="7593555694782789615" datatype="html">
<source>Open</source> <source>Open</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">251</context> <context context-type="linenumber">250</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
@@ -2247,21 +2240,21 @@
<source>Input data</source> <source>Input data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">261</context> <context context-type="linenumber">260</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1585185618099050920" datatype="html"> <trans-unit id="1585185618099050920" datatype="html">
<source>Result data</source> <source>Result data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">267</context> <context context-type="linenumber">266</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7976920528153858271" datatype="html"> <trans-unit id="7976920528153858271" datatype="html">
<source>No tasks match the current filters.</source> <source>No tasks match the current filters.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context> <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">289</context> <context context-type="linenumber">288</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2525230676386818985" datatype="html"> <trans-unit id="2525230676386818985" datatype="html">
@@ -2520,11 +2513,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">256</context> <context context-type="linenumber">255</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">259</context> <context context-type="linenumber">258</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3818027200170621545" datatype="html"> <trans-unit id="3818027200170621545" datatype="html">
@@ -2877,11 +2870,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">287</context> <context context-type="linenumber">286</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">289</context> <context context-type="linenumber">288</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4569276013106377105" datatype="html"> <trans-unit id="4569276013106377105" datatype="html">
@@ -3218,11 +3211,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">317</context> <context context-type="linenumber">316</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">320</context> <context context-type="linenumber">319</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="472206565520537964" datatype="html"> <trans-unit id="472206565520537964" datatype="html">
@@ -3247,36 +3240,36 @@
<source>Close all</source> <source>Close all</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">163</context> <context context-type="linenumber">162</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">165</context> <context context-type="linenumber">164</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3897348120591552265" datatype="html"> <trans-unit id="3897348120591552265" datatype="html">
<source>Manage</source> <source>Manage</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">174</context> <context context-type="linenumber">173</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8008131619909556709" datatype="html"> <trans-unit id="8008131619909556709" datatype="html">
<source>Attributes</source> <source>Attributes</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">181</context> <context context-type="linenumber">180</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">183</context> <context context-type="linenumber">182</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7437910965833684826" datatype="html"> <trans-unit id="7437910965833684826" datatype="html">
<source>Correspondents</source> <source>Correspondents</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">209</context> <context context-type="linenumber">208</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
@@ -3295,7 +3288,7 @@
<source>Document types</source> <source>Document types</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">214</context> <context context-type="linenumber">213</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context> <context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
@@ -3306,7 +3299,7 @@
<source>Storage paths</source> <source>Storage paths</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">219</context> <context context-type="linenumber">218</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context> <context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
@@ -3317,7 +3310,7 @@
<source>Custom fields</source> <source>Custom fields</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">224</context> <context context-type="linenumber">223</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -3336,11 +3329,11 @@
<source>Workflows</source> <source>Workflows</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">242</context> <context context-type="linenumber">241</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">244</context> <context context-type="linenumber">243</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context> <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
@@ -3351,71 +3344,71 @@
<source>Mail</source> <source>Mail</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">249</context> <context context-type="linenumber">248</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">252</context> <context context-type="linenumber">251</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7844706011418789951" datatype="html"> <trans-unit id="7844706011418789951" datatype="html">
<source>Administration</source> <source>Administration</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">267</context> <context context-type="linenumber">266</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3008420115644088420" datatype="html"> <trans-unit id="3008420115644088420" datatype="html">
<source>Configuration</source> <source>Configuration</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">280</context> <context context-type="linenumber">279</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">282</context> <context context-type="linenumber">281</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1534029177398918729" datatype="html"> <trans-unit id="1534029177398918729" datatype="html">
<source>GitHub</source> <source>GitHub</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">327</context> <context context-type="linenumber">326</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4112664765954374539" datatype="html"> <trans-unit id="4112664765954374539" datatype="html">
<source>is available.</source> <source>is available.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">336,337</context> <context context-type="linenumber">335,336</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1175891574282637937" datatype="html"> <trans-unit id="1175891574282637937" datatype="html">
<source>Click to view.</source> <source>Click to view.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">337</context> <context context-type="linenumber">336</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9811291095862612" datatype="html"> <trans-unit id="9811291095862612" datatype="html">
<source>Paperless-ngx can automatically check for updates</source> <source>Paperless-ngx can automatically check for updates</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">341</context> <context context-type="linenumber">340</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="894819944961861800" datatype="html"> <trans-unit id="894819944961861800" datatype="html">
<source> How does this work? </source> <source> How does this work? </source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">348,350</context> <context context-type="linenumber">347,349</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="509090351011426949" datatype="html"> <trans-unit id="509090351011426949" datatype="html">
<source>Update available</source> <source>Update available</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">361</context> <context context-type="linenumber">360</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1542489069631984294" datatype="html"> <trans-unit id="1542489069631984294" datatype="html">
@@ -3611,17 +3604,6 @@
<context context-type="linenumber">20</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8066608938393600549" datatype="html">
<source>Message</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/chat/chat/chat.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="3167745892412525370" datatype="html"> <trans-unit id="3167745892412525370" datatype="html">
<source>Ask a question about this document...</source> <source>Ask a question about this document...</source>
<context-group purpose="location"> <context-group purpose="location">
@@ -5846,6 +5828,13 @@
<context context-type="linenumber">32</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8066608938393600549" datatype="html">
<source>Message</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="5867799091834207531" datatype="html"> <trans-unit id="5867799091834207531" datatype="html">
<source>Use archive version</source> <source>Use archive version</source>
<context-group purpose="location"> <context-group purpose="location">
@@ -6705,7 +6694,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">88</context> <context context-type="linenumber">87</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
@@ -6782,25 +6771,18 @@
<context context-type="linenumber">66</context> <context context-type="linenumber">66</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4306169234658044987" datatype="html">
<source>Share link</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="2618101617221155143" datatype="html"> <trans-unit id="2618101617221155143" datatype="html">
<source>Copy link</source> <source>Copy link</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">82</context> <context context-type="linenumber">81</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8372007266188249803" datatype="html"> <trans-unit id="8372007266188249803" datatype="html">
<source>Never</source> <source>Never</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">94</context> <context context-type="linenumber">93</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
@@ -6815,7 +6797,7 @@
<source>File version</source> <source>File version</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">96</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
@@ -6826,7 +6808,7 @@
<source>Size</source> <source>Size</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">100</context> <context context-type="linenumber">99</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
@@ -6837,14 +6819,14 @@
<source>A zip file containing the selected documents will be created for this share link bundle. This process happens in the background and may take some time, especially for large bundles.</source> <source>A zip file containing the selected documents will be created for this share link bundle. This process happens in the background and may take some time, especially for large bundles.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">110</context> <context context-type="linenumber">109</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7274497464693086242" datatype="html"> <trans-unit id="7274497464693086242" datatype="html">
<source>Manage share link bundles</source> <source>Manage share link bundles</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">114</context> <context context-type="linenumber">113</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -7835,8 +7817,8 @@
<context context-type="linenumber">314</context> <context context-type="linenumber">314</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2659735245739197634" datatype="html"> <trans-unit id="7985558498848210210" datatype="html">
<source>Original SHA256 checksum</source> <source>Original MD5 checksum</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">318</context> <context context-type="linenumber">318</context>
@@ -7856,8 +7838,8 @@
<context context-type="linenumber">326</context> <context context-type="linenumber">326</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6714358112223607756" datatype="html"> <trans-unit id="342875990758166588" datatype="html">
<source>Archive SHA256 checksum</source> <source>Archive MD5 checksum</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">331</context> <context context-type="linenumber">331</context>
@@ -8255,67 +8237,63 @@
<source>Optional</source> <source>Optional</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">16</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="787787001194154763" datatype="html"> <trans-unit id="787787001194154763" datatype="html">
<source>Add new version</source> <source>Add new version</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">32</context> <context context-type="linenumber">31</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3119565892291077820" datatype="html"> <trans-unit id="3119565892291077820" datatype="html">
<source>Uploading version...</source> <source>Uploading version...</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">38</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8538204518127762311" datatype="html"> <trans-unit id="8538204518127762311" datatype="html">
<source>Processing version...</source> <source>Processing version...</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">45</context> <context context-type="linenumber">44</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6587183611950398104" datatype="html"> <trans-unit id="6587183611950398104" datatype="html">
<source>Version upload failed.</source> <source>Version upload failed.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">50</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6328986243040954523" datatype="html"> <trans-unit id="6328986243040954523" datatype="html">
<source>Version label</source> <source>Version label</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">75</context> <context context-type="linenumber">80</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">85</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2724055831234181057" datatype="html"> <trans-unit id="2724055831234181057" datatype="html">
<source>Version</source> <source>Version</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">96</context> <context context-type="linenumber">91</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2491751767883178298" datatype="html"> <trans-unit id="2491751767883178298" datatype="html">
<source>Delete this version?</source> <source>Delete this version?</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">127</context> <context context-type="linenumber">122</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="894108684749537200" datatype="html"> <trans-unit id="894108684749537200" datatype="html">
<source>Delete version</source> <source>Delete version</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">132</context> <context context-type="linenumber">127</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9111284310237278846" datatype="html"> <trans-unit id="9111284310237278846" datatype="html">
@@ -10218,13 +10196,6 @@
<context context-type="linenumber">20</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8669280833944376006" datatype="html">
<source>Select all processed email messages</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="8691920320483720007" datatype="html"> <trans-unit id="8691920320483720007" datatype="html">
<source>Received</source> <source>Received</source>
<context-group purpose="location"> <context-group purpose="location">
+1856 -1256
View File
File diff suppressed because it is too large Load Diff
@@ -5,9 +5,8 @@
i18n-info> i18n-info>
<div class="input-group input-group-sm align-items-center"> <div class="input-group input-group-sm align-items-center">
<div class="input-group input-group-sm me-3"> <div class="input-group input-group-sm me-3">
<label class="input-group-text text-muted" for="logLineLimit" i18n>Show</label> <span class="input-group-text text-muted" i18n>Show</span>
<input <input
id="logLineLimit"
class="form-control" class="form-control"
type="number" type="number"
min="100" min="100"
@@ -18,7 +17,7 @@
<span class="input-group-text text-muted" i18n>lines</span> <span class="input-group-text text-muted" i18n>lines</span>
</div> </div>
<div class="form-check form-switch mt-1"> <div class="form-check form-switch mt-1">
<input id="autoRefreshSwitch" class="form-check-input" type="checkbox" role="switch" [ngModel]="autoRefreshEnabled()" (ngModelChange)="autoRefreshEnabled.set($event)"> <input class="form-check-input" type="checkbox" role="switch" [ngModel]="autoRefreshEnabled()" (ngModelChange)="autoRefreshEnabled.set($event)">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label> <label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div> </div>
</div> </div>
@@ -100,7 +100,9 @@ export class LogsComponent
parsed.some((log, idx) => { parsed.some((log, idx) => {
const current = this.logs()[idx] const current = this.logs()[idx]
return ( return (
current?.message !== log.message || current?.level !== log.level !current ||
current.message !== log.message ||
current.level !== log.level
) )
}) })
if (hasChanges) { if (hasChanges) {
@@ -150,10 +150,10 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-3 col-form-label pt-0"> <div class="col-md-3 col-form-label pt-0">
<label for="searchLink" i18n>Full search links to</label> <span i18n>Full search links to</span>
</div> </div>
<div class="col mb-3"> <div class="col mb-3">
<select id="searchLink" class="form-select" formControlName="searchLink"> <select class="form-select" formControlName="searchLink">
<option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option> <option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option>
<option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option> <option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option>
</select> </select>
@@ -199,10 +199,10 @@
<h5 i18n>Documents</h5> <h5 i18n>Documents</h5>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-3 col-form-label pt-0"> <div class="col-md-3 col-form-label pt-0">
<label for="documentListItemPerPage" i18n>Items per page</label> <span i18n>Items per page</span>
</div> </div>
<div class="col"> <div class="col">
<select id="documentListItemPerPage" class="form-select" formControlName="documentListItemPerPage"> <select class="form-select" formControlName="documentListItemPerPage">
<option [ngValue]="10">10</option> <option [ngValue]="10">10</option>
<option [ngValue]="25">25</option> <option [ngValue]="25">25</option>
<option [ngValue]="50">50</option> <option [ngValue]="50">50</option>
@@ -274,10 +274,10 @@
<h5 class="mt-3" i18n>PDF Editor</h5> <h5 class="mt-3" i18n>PDF Editor</h5>
<div class="row"> <div class="row">
<div class="col-md-3 col-form-label pt-0"> <div class="col-md-3 col-form-label pt-0">
<label for="pdfEditorDefaultEditMode" i18n>Default editing mode</label> <span i18n>Default editing mode</span>
</div> </div>
<div class="col"> <div class="col">
<select id="pdfEditorDefaultEditMode" class="form-select" formControlName="pdfEditorDefaultEditMode"> <select class="form-select" formControlName="pdfEditorDefaultEditMode">
<option [ngValue]="PdfEditorEditMode.Create" i18n>Create new document(s)</option> <option [ngValue]="PdfEditorEditMode.Create" i18n>Create new document(s)</option>
<option [ngValue]="PdfEditorEditMode.Update" i18n>Add document version</option> <option [ngValue]="PdfEditorEditMode.Update" i18n>Add document version</option>
</select> </select>
@@ -399,17 +399,17 @@ describe('SettingsComponent', () => {
completeSetup() completeSetup()
const field = 'storage_path' const field = 'storage_path'
expect( expect(
component.settingsForm.get('documentDetailsHiddenFields').value component.settingsForm.get('documentDetailsHiddenFields').value.length
).toHaveLength(0) ).toEqual(0)
component.toggleDocumentDetailField(field, false) component.toggleDocumentDetailField(field, false)
expect( expect(
component.settingsForm.get('documentDetailsHiddenFields').value component.settingsForm.get('documentDetailsHiddenFields').value.length
).toHaveLength(1) ).toEqual(1)
expect(component.isDocumentDetailFieldShown(field)).toBeFalsy() expect(component.isDocumentDetailFieldShown(field)).toBeFalsy()
component.toggleDocumentDetailField(field, true) component.toggleDocumentDetailField(field, true)
expect( expect(
component.settingsForm.get('documentDetailsHiddenFields').value component.settingsForm.get('documentDetailsHiddenFields').value.length
).toHaveLength(0) ).toEqual(0)
expect(component.isDocumentDetailFieldShown(field)).toBeTruthy() expect(component.isDocumentDetailFieldShown(field)).toBeTruthy()
}) })
}) })
@@ -15,7 +15,7 @@
<i-bs name="check2-all" class="me-1"></i-bs><ng-container i18n>Dismiss all</ng-container> <i-bs name="check2-all" class="me-1"></i-bs><ng-container i18n>Dismiss all</ng-container>
</button> </button>
<div class="form-check form-switch mb-0 ms-2"> <div class="form-check form-switch mb-0 ms-2">
<input id="autoRefreshSwitch" class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled"> <input class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label> <label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div> </div>
</div> </div>
@@ -93,8 +93,7 @@
<i-bs width="1em" height="1em" name="x"></i-bs> <i-bs width="1em" height="1em" name="x"></i-bs>
</button> </button>
} }
<label class="visually-hidden" for="taskSearch" i18n>Search tasks</label> <input #filterInput class="form-control form-control-sm" type="text"
<input #filterInput id="taskSearch" class="form-control form-control-sm" type="text"
(keyup)="filterInputKeyup($event)" (keyup)="filterInputKeyup($event)"
[(ngModel)]="filterText"> [(ngModel)]="filterText">
</div> </div>
@@ -212,7 +211,7 @@
<i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs> <i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs>
</button> </button>
</td> </td>
<td class="actions-col"> <td scope="row" class="actions-col">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }"> <button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
<i-bs name="check" class="me-1"></i-bs><ng-container i18n>Dismiss</ng-container> <i-bs name="check" class="me-1"></i-bs><ng-container i18n>Dismiss</ng-container>
@@ -150,8 +150,7 @@
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
popoverClass="popover-slim"> popoverClass="popover-slim">
<i-bs class="me-2" name="file-text"></i-bs><span>{{d.title | documentTitle}}</span> <i-bs class="me-2" name="file-text"></i-bs><span>{{d.title | documentTitle}}</span>
<span class="close flex-column justify-content-center" <span class="close flex-column justify-content-center" (click)="closeDocument(d); $event.preventDefault()">
(click)="$event.preventDefault(); $event.stopPropagation(); closeDocument(d)">
<i-bs name="x"></i-bs> <i-bs name="x"></i-bs>
</span> </span>
</a> </a>
@@ -29,11 +29,9 @@
</div> </div>
<form class="chat-input"> <form class="chat-input">
<label class="visually-hidden" for="chatInput" i18n>Message</label>
<div class="input-group"> <div class="input-group">
<input <input
#chatInput #chatInput
id="chatInput"
class="form-control form-control-sm" name="chatInput" type="text" class="form-control form-control-sm" name="chatInput" type="text"
[placeholder]="placeholder" [placeholder]="placeholder"
[disabled]="loading" [disabled]="loading"
@@ -76,7 +76,7 @@ describe('ChatComponent', () => {
component.input = 'Hello' component.input = 'Hello'
component.sendMessage() component.sendMessage()
expect(component.messages).toHaveLength(2) expect(component.messages.length).toBe(2)
expect(component.messages[0].content).toBe('Hello') expect(component.messages[0].content).toBe('Hello')
expect(component.loading).toBe(true) expect(component.loading).toBe(true)
@@ -158,7 +158,7 @@ describe('ChatComponent', () => {
const message = { content: '', role: 'assistant', isStreaming: true } const message = { content: '', role: 'assistant', isStreaming: true }
component.enqueueTypewriter(null, message as any) // coverage for null component.enqueueTypewriter(null, message as any) // coverage for null
component.enqueueTypewriter('Hello', message as any) component.enqueueTypewriter('Hello', message as any)
expect(component['typewriterBuffer']).toHaveLength(4) expect(component['typewriterBuffer'].length).toBe(4)
}) })
it('should scroll to bottom after sending a message', () => { it('should scroll to bottom after sending a message', () => {
@@ -180,7 +180,7 @@
<div class="col"> <div class="col">
<div class="trigger-filters mb-3"> <div class="trigger-filters mb-3">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="form-label mb-0" i18n>Advanced Filters</span> <label class="form-label mb-0" i18n>Advanced Filters</label>
<button <button
type="button" type="button"
class="btn btn-sm btn-outline-primary ms-auto" class="btn btn-sm btn-outline-primary ms-auto"
@@ -401,7 +401,7 @@ describe('WorkflowEditDialogComponent', () => {
component.addFilter(triggerGroup as FormGroup) component.addFilter(triggerGroup as FormGroup)
const filters = component.getFiltersFormArray(triggerGroup as FormGroup) const filters = component.getFiltersFormArray(triggerGroup as FormGroup)
expect(filters).toHaveLength(3) expect(filters.length).toBe(3)
filters.at(0).get('values').setValue([1]) filters.at(0).get('values').setValue([1])
filters.at(1).get('values').setValue([2, 3]) filters.at(1).get('values').setValue([2, 3])
@@ -672,7 +672,7 @@ describe('WorkflowEditDialogComponent', () => {
} as any } as any
const filters = component['buildFiltersFormArray'](trigger) const filters = component['buildFiltersFormArray'](trigger)
expect(filters).toHaveLength(0) expect(filters.length).toBe(0)
component.filterDefinitions = originalDefinitions component.filterDefinitions = originalDefinitions
}) })
@@ -739,7 +739,7 @@ describe('WorkflowEditDialogComponent', () => {
component.ngOnInit() component.ngOnInit()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
const filters = component.getFiltersFormArray(triggerGroup) const filters = component.getFiltersFormArray(triggerGroup)
expect(filters).toHaveLength(13) expect(filters.length).toBe(13)
const customFieldFilter = filters.at(12) as FormGroup const customFieldFilter = filters.at(12) as FormGroup
expect(customFieldFilter.get('type').value).toBe( expect(customFieldFilter.get('type').value).toBe(
TriggerFilterType.CustomFieldQuery TriggerFilterType.CustomFieldQuery
@@ -969,12 +969,12 @@ describe('WorkflowEditDialogComponent', () => {
component.addFilter(triggerGroup) component.addFilter(triggerGroup)
component.removeFilter(triggerGroup, 0) component.removeFilter(triggerGroup, 0)
expect(component.getFiltersFormArray(triggerGroup)).toHaveLength(0) expect(component.getFiltersFormArray(triggerGroup).length).toBe(0)
component.addFilter(triggerGroup) component.addFilter(triggerGroup)
const filterArrayAfterAdd = component.getFiltersFormArray(triggerGroup) const filterArrayAfterAdd = component.getFiltersFormArray(triggerGroup)
filterArrayAfterAdd.at(0).get('type').setValue(TriggerFilterType.TagsAll) filterArrayAfterAdd.at(0).get('type').setValue(TriggerFilterType.TagsAll)
expect(component.getFiltersFormArray(triggerGroup)).toHaveLength(1) expect(component.getFiltersFormArray(triggerGroup).length).toBe(1)
}) })
it('should remove selected custom field from the form group', () => { it('should remove selected custom field from the form group', () => {
@@ -933,7 +933,7 @@ export class WorkflowEditDialogComponent
getFilterSelectItems(type: TriggerFilterType) { getFilterSelectItems(type: TriggerFilterType) {
const definition = this.getFilterDefinition(type) const definition = this.getFilterDefinition(type)
if (definition?.inputType !== 'select') { if (!definition || definition.inputType !== 'select') {
return [] return []
} }
@@ -157,8 +157,8 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
private removeChildren(tagIDs: number[], tag: Tag) { private removeChildren(tagIDs: number[], tag: Tag) {
if (tag.children?.length) { if (tag.children?.length) {
const childIDs = new Set(tag.children.map((child) => child.id)) const childIDs = tag.children.map((child) => child.id)
tagIDs = tagIDs.filter((id) => !childIDs.has(id)) tagIDs = tagIDs.filter((id) => !childIDs.includes(id))
for (const child of tag.children) { for (const child of tag.children) {
tagIDs = this.removeChildren(tagIDs, child) tagIDs = this.removeChildren(tagIDs, child)
} }
@@ -14,7 +14,7 @@ import { environment } from 'src/environments/environment'
}) })
export class PageHeaderComponent { export class PageHeaderComponent {
private titleService = inject(Title) private titleService = inject(Title)
private readonly clipboard = inject(Clipboard) private clipboard = inject(Clipboard)
readonly id = input<number>(undefined) readonly id = input<number>(undefined)
readonly subTitle = input('') readonly subTitle = input('')
@@ -49,7 +49,8 @@ describe('PngxPdfViewerComponent', () => {
new URL('assets/js/pdf.worker.min.mjs', document.baseURI).toString() new URL('assets/js/pdf.worker.min.mjs', document.baseURI).toString()
) )
const isVisible = (component as any).findController.onIsPageVisible as const isVisible = (component as any).findController.onIsPageVisible as
(() => boolean) | undefined | (() => boolean)
| undefined
expect(isVisible?.()).toBe(true) expect(isVisible?.()).toBe(true)
expect(loadSpy).toHaveBeenCalledWith( expect(loadSpy).toHaveBeenCalledWith(
expect.objectContaining({ numPages: 1 }) expect.objectContaining({ numPages: 1 })
@@ -60,7 +61,6 @@ describe('PngxPdfViewerComponent', () => {
it('resolves the worker source relative to the document base URI', async () => { it('resolves the worker source relative to the document base URI', async () => {
setBaseHref('/paperless/') setBaseHref('/paperless/')
const getDocumentSpy = jest.spyOn(pdfjs, 'getDocument')
await initComponent() await initComponent()
@@ -70,13 +70,6 @@ describe('PngxPdfViewerComponent', () => {
expect(pdfjs.GlobalWorkerOptions.workerSrc).toContain( expect(pdfjs.GlobalWorkerOptions.workerSrc).toContain(
'/paperless/assets/js/pdf.worker.min.mjs' '/paperless/assets/js/pdf.worker.min.mjs'
) )
expect(getDocumentSpy).toHaveBeenCalledWith({
url: 'test.pdf',
password: undefined,
withCredentials: true,
wasmUrl: expect.stringContaining('/paperless/assets/wasm/'),
iccUrl: expect.stringContaining('/paperless/assets/iccs/'),
})
}) })
it('initializes single-page viewer and disables text layer', async () => { it('initializes single-page viewer and disables text layer', async () => {
@@ -174,12 +174,10 @@ export class PngxPdfViewerComponent
'assets/js/pdf.worker.min.mjs', 'assets/js/pdf.worker.min.mjs',
this.document.baseURI this.document.baseURI
).toString() ).toString()
const initOptions = { let initOptions = {
url: this.src, url: this.src,
password: this.password, password: this.password,
withCredentials: true, withCredentials: true,
wasmUrl: new URL('assets/wasm/', this.document.baseURI).toString(),
iccUrl: new URL('assets/iccs/', this.document.baseURI).toString(),
} }
this.loadingTask = getDocument(initOptions) this.loadingTask = getDocument(initOptions)
try { try {
@@ -65,9 +65,8 @@
<dd class="col-sm-8"><code>{{ createdBundle.slug }}</code></dd> <dd class="col-sm-8"><code>{{ createdBundle.slug }}</code></dd>
<dt class="col-sm-4" i18n>Link</dt> <dt class="col-sm-4" i18n>Link</dt>
<dd class="col-sm-8"> <dd class="col-sm-8">
<label class="visually-hidden" for="shareBundleLink" i18n>Share link</label>
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input id="shareBundleLink" class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly> <input class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
<button <button
class="btn btn-outline-primary" class="btn btn-outline-primary"
type="button" type="button"
@@ -315,7 +315,7 @@
<td>{{metadata()?.original_filename}}</td> <td>{{metadata()?.original_filename}}</td>
</tr> </tr>
<tr> <tr>
<td i18n>Original SHA256 checksum</td> <td i18n>Original MD5 checksum</td>
<td>{{metadata()?.original_checksum}}</td> <td>{{metadata()?.original_checksum}}</td>
</tr> </tr>
<tr> <tr>
@@ -328,7 +328,7 @@
</tr> </tr>
@if (metadata()?.has_archive_version) { @if (metadata()?.has_archive_version) {
<tr> <tr>
<td i18n>Archive SHA256 checksum</td> <td i18n>Archive MD5 checksum</td>
<td>{{metadata()?.archive_checksum}}</td> <td>{{metadata()?.archive_checksum}}</td>
</tr> </tr>
} }
@@ -510,7 +510,7 @@ describe('DocumentDetailComponent', () => {
const deleteButtons = fixture.debugElement.queryAll( const deleteButtons = fixture.debugElement.queryAll(
By.css('pngx-confirm-button') By.css('pngx-confirm-button')
) )
expect(deleteButtons).toHaveLength(1) expect(deleteButtons.length).toEqual(1)
}) })
it('should fall back to details tab when duplicates tab is active but no duplicates', () => { it('should fall back to details tab when duplicates tab is active but no duplicates', () => {
@@ -1,6 +1,6 @@
@if (loading()) { @if (loading()) {
<div class="d-flex"> <div class="d-flex">
<output class="spinner-border spinner-border-sm fw-normal"></output> <output class="spinner-border spinner-border-sm fw-normal" role="status"></output>
</div> </div>
} @else { } @else {
<ul class="list-group"> <ul class="list-group">
@@ -26,11 +26,11 @@ import { UserService } from 'src/app/services/rest/user.service'
], ],
}) })
export class DocumentHistoryComponent implements OnInit { export class DocumentHistoryComponent implements OnInit {
private readonly documentService = inject(DocumentService) private documentService = inject(DocumentService)
private readonly correspondentService = inject(CorrespondentService) private correspondentService = inject(CorrespondentService)
private readonly storagePathService = inject(StoragePathService) private storagePathService = inject(StoragePathService)
private readonly documentTypeService = inject(DocumentTypeService) private documentTypeService = inject(DocumentTypeService)
private readonly userService = inject(UserService) private userService = inject(UserService)
public AuditLogAction = AuditLogAction public AuditLogAction = AuditLogAction
@@ -70,7 +70,7 @@ export class DocumentHistoryComponent implements OnInit {
return cached return cached
} }
const idInt = Number.parseInt(id, 10) const idInt = parseInt(id, 10)
const fallback$ = of(id) const fallback$ = of(id)
let result$: Observable<string> let result$: Observable<string>
@@ -7,9 +7,8 @@
<div class="px-3 py-2 mb-2"> <div class="px-3 py-2 mb-2">
@if (versionUploadState === UploadState.Idle) { @if (versionUploadState === UploadState.Idle) {
<div class="input-group input-group-sm mb-2"> <div class="input-group input-group-sm mb-2">
<label class="input-group-text" for="newVersionLabel" i18n>Label</label> <span class="input-group-text" i18n>Label</span>
<input <input
id="newVersionLabel"
class="form-control" class="form-control"
type="text" type="text"
[(ngModel)]="newVersionLabel" [(ngModel)]="newVersionLabel"
@@ -71,13 +70,9 @@
} }
</div> </div>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
@if (isEditingVersion(version.id)) {
<label class="visually-hidden" for="versionLabel{{ version.id }}" i18n>Version label</label>
}
<div class="input-group input-group-sm mb-1"> <div class="input-group input-group-sm mb-1">
@if (isEditingVersion(version.id)) { @if (isEditingVersion(version.id)) {
<input <input
id="versionLabel{{ version.id }}"
class="form-control" class="form-control"
type="text" type="text"
[(ngModel)]="versionLabelDraft" [(ngModel)]="versionLabelDraft"
@@ -328,7 +328,7 @@ describe('FilterEditorComponent', () => {
}) })
it('should ingest legacy text filter rules for doc title + content', () => { it('should ingest legacy text filter rules for doc title + content', () => {
expect(component.textFilter).toBeNull() expect(component.textFilter).toEqual(null)
component.filterRules = [ component.filterRules = [
{ {
rule_type: FILTER_TITLE_CONTENT, rule_type: FILTER_TITLE_CONTENT,
@@ -1,18 +1,18 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col mb-2 mb-xl-0"> <div class="col mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center"> <div class="form-inline d-flex align-items-center">
<label class="text-muted me-2 mb-0" for="managementNameFilter" i18n>Filter by:</label> <label class="text-muted me-2 mb-0" i18n>Filter by:</label>
<input id="managementNameFilter" class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder> <input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
</div> </div>
</div> </div>
<div class="col-auto mb-2 mb-xl-0"> <div class="col-auto mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center"> <div class="form-inline d-flex align-items-center">
<div class="input-group input-group-sm w-auto d-none d-md-flex"> <div class="input-group input-group-sm w-auto d-none d-md-flex">
<label class="input-group-text border-0" for="managementPageSize" i18n>Show:</label> <span class="input-group-text border-0" i18n>Show:</span>
</div> </div>
<div class="input-group input-group-sm w-auto me-3"> <div class="input-group input-group-sm w-auto me-3">
<select id="managementPageSize" class="form-select form-select-sm small" [(ngModel)]="pageSize"> <select class="form-select form-select-sm small" [(ngModel)]="pageSize">
<option [ngValue]="25">25</option> <option [ngValue]="25">25</option>
<option [ngValue]="50">50</option> <option [ngValue]="50">50</option>
<option [ngValue]="100">100</option> <option [ngValue]="100">100</option>
@@ -83,12 +83,12 @@ describe('TagListComponent', () => {
] ]
component['_nameFilter'] = null // Simulate empty name filter component['_nameFilter'] = null // Simulate empty name filter
const filtered = component.filterData(tags as any) const filtered = component.filterData(tags as any)
expect(filtered).toHaveLength(2) expect(filtered.length).toBe(2)
expect(filtered.find((t) => t.id === 2)).toBeUndefined() expect(filtered.find((t) => t.id === 2)).toBeUndefined()
component['_nameFilter'] = 'Tag2' // Simulate non-empty name filter component['_nameFilter'] = 'Tag2' // Simulate non-empty name filter
const filteredWithName = component.filterData(tags as any) const filteredWithName = component.filterData(tags as any)
expect(filteredWithName).toHaveLength(2) expect(filteredWithName.length).toBe(2)
expect(filteredWithName.find((t) => t.id === 2)).toBeUndefined() expect(filteredWithName.find((t) => t.id === 2)).toBeUndefined()
expect( expect(
filteredWithName filteredWithName
@@ -26,7 +26,7 @@
<th scope="col" style="width: 40px;"> <th scope="col" style="width: 40px;">
<div class="form-check m-0 ms-2 me-n2"> <div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="all-objects" [ngModel]="toggleAllEnabled()" (ngModelChange)="toggleAllEnabled.set($event)" [disabled]="processedMails().length === 0" (click)="toggleAll($event); $event.stopPropagation();"> <input type="checkbox" class="form-check-input" id="all-objects" [ngModel]="toggleAllEnabled()" (ngModelChange)="toggleAllEnabled.set($event)" [disabled]="processedMails().length === 0" (click)="toggleAll($event); $event.stopPropagation();">
<label class="form-check-label" for="all-objects"><span class="visually-hidden" i18n>Select all processed email messages</span></label> <label class="form-check-label" for="all-objects"></label>
</div> </div>
</th> </th>
<th scope="col" i18n>Subject</th> <th scope="col" i18n>Subject</th>
@@ -5,6 +5,6 @@
pre { pre {
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: break-word; word-break: break-word;
} }
} }
+6 -1
View File
@@ -1,7 +1,12 @@
import { normalizeSync } from 'normalize-diacritics' import { normalizeSync } from 'normalize-diacritics'
export type SearchTextValue = export type SearchTextValue =
string | number | boolean | bigint | null | undefined | string
| number
| boolean
| bigint
| null
| undefined
export function normalizeSearchText(value: SearchTextValue): string { export function normalizeSearchText(value: SearchTextValue): string {
return normalizeSync(String(value ?? '')).toLocaleLowerCase() return normalizeSync(String(value ?? '')).toLocaleLowerCase()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -8
View File
@@ -771,14 +771,7 @@ class CustomFieldQueryFilter(Filter):
) )
q, annotations = parser.parse(value) q, annotations = parser.parse(value)
# The Count(...) annotations above require a GROUP BY/HAVING to evaluate. return qs.annotate(**annotations).filter(q)
# Applying them directly to `qs` mixes that HAVING with `qs`'s existing
# joins (e.g. repeated tag joins from tags__id__all, the object-permission
# OR-filter), which some backends (e.g. MariaDB) fail to plan correctly,
# raising "Unknown column ... in 'HAVING'". Evaluating the annotation on
# an isolated queryset keeps the GROUP BY/HAVING self-contained.
matching_ids = Document.objects.annotate(**annotations).filter(q).values("pk")
return qs.filter(pk__in=matching_ids)
class DocumentFilterSet(FilterSet): class DocumentFilterSet(FilterSet):
@@ -45,18 +45,6 @@ class Command(PaperlessCommand):
"Safe to run on every startup or upgrade." "Safe to run on every startup or upgrade."
), ),
) )
parser.add_argument(
"--heap-size-mb",
type=int,
default=None,
help=(
"Tantivy writer memory budget in MB for a full reindex "
"(split across writer threads). Defaults to 512MB; lower "
"this on memory-constrained hosts. Larger values buffer "
"more documents before flushing a segment, deferring "
"merge work rather than avoiding it."
),
)
def handle(self, *args, **options): def handle(self, *args, **options):
with transaction.atomic(): with transaction.atomic():
@@ -72,26 +60,13 @@ class Command(PaperlessCommand):
"document_type", "document_type",
"storage_path", "storage_path",
"owner", "owner",
).prefetch_related( ).prefetch_related("tags", "notes", "custom_fields", "versions")
"tags",
"notes__user",
"custom_fields__field",
"versions",
)
total = documents.count()
rebuild_kwargs = {}
if options.get("heap_size_mb") is not None:
rebuild_kwargs["writer_heap_bytes"] = (
options["heap_size_mb"] * 1_000_000
)
get_backend().rebuild( get_backend().rebuild(
documents, documents,
iter_wrapper=lambda pairs: self.track( iter_wrapper=lambda docs: self.track(
pairs, docs,
description="Indexing documents...", description="Indexing documents...",
total=total,
), ),
**rebuild_kwargs,
) )
reset_backend() reset_backend()
+36 -140
View File
@@ -8,7 +8,6 @@ import time
from datetime import UTC from datetime import UTC
from datetime import datetime from datetime import datetime
from enum import StrEnum from enum import StrEnum
from itertools import islice
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Final from typing import Final
from typing import Self from typing import Self
@@ -17,14 +16,13 @@ from typing import TypeVar
from typing import cast from typing import cast
import filelock import filelock
import regex
import tantivy import tantivy
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.utils.timezone import get_current_timezone from django.utils.timezone import get_current_timezone
from guardian.shortcuts import get_users_with_perms from guardian.shortcuts import get_users_with_perms
from documents.search._query import build_permission_filter from documents.search._query import build_permission_filter
from documents.search._query import extract_cjk_text
from documents.search._query import parse_simple_text_highlight_query from documents.search._query import parse_simple_text_highlight_query
from documents.search._query import parse_simple_text_query from documents.search._query import parse_simple_text_query
from documents.search._query import parse_simple_title_query from documents.search._query import parse_simple_title_query
@@ -34,14 +32,11 @@ from documents.search._schema import build_schema
from documents.search._schema import open_or_rebuild_index from documents.search._schema import open_or_rebuild_index
from documents.search._schema import wipe_index from documents.search._schema import wipe_index
from documents.search._tokenizer import ascii_fold from documents.search._tokenizer import ascii_fold
from documents.search._tokenizer import autocomplete_tokens
from documents.search._tokenizer import register_tokenizers from documents.search._tokenizer import register_tokenizers
from documents.utils import IterWrapper from documents.utils import IterWrapper
from documents.utils import identity from documents.utils import identity
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Iterator
from collections.abc import Sequence
from pathlib import Path from pathlib import Path
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
@@ -56,6 +51,9 @@ _LOCK_RETRY_ATTEMPTS: Final[int] = 4 # total attempts (1 initial + 3 retries)
_LOCK_BACKOFF_BASE: Final[float] = 1.0 # seconds _LOCK_BACKOFF_BASE: Final[float] = 1.0 # seconds
_LOCK_BACKOFF_CAP: Final[float] = 10.0 # seconds _LOCK_BACKOFF_CAP: Final[float] = 10.0 # seconds
_WORD_RE = regex.compile(r"\w+")
_AUTOCOMPLETE_REGEX_TIMEOUT = 1.0 # seconds; guards against ReDoS on untrusted content
T = TypeVar("T") T = TypeVar("T")
@@ -68,16 +66,25 @@ class SearchMode(StrEnum):
def _extract_autocomplete_words(text_sources: list[str]) -> set[str]: def _extract_autocomplete_words(text_sources: list[str]) -> set[str]:
"""Extract and normalize words for autocomplete. """Extract and normalize words for autocomplete.
Tokenizes with Tantivy's simple analyzer (simple -> lowercase -> ascii_fold) Splits on non-word characters (matching Tantivy's simple tokenizer), lowercases,
so the extracted words match how document content is indexed, and runs the and ascii-folds each token. Uses the regex library with a timeout to guard against
whole pass in Rust. This replaces a Python regex scan plus per-token folding, ReDoS on untrusted document content.
which dominated full-reindex CPU time. Tokenizing natively also removes the
ReDoS exposure of running a regex over untrusted content.
""" """
words = set() words = set()
for text in text_sources: for text in text_sources:
if text: if not text:
words.update(autocomplete_tokens(text)) continue
try:
tokens = _WORD_RE.findall(text, timeout=_AUTOCOMPLETE_REGEX_TIMEOUT)
except TimeoutError: # pragma: no cover
logger.warning(
"Autocomplete word extraction timed out for a text source; skipping.",
)
continue
for token in tokens:
normalized = ascii_fold(token.lower())
if normalized:
words.add(normalized)
return words return words
@@ -380,7 +387,6 @@ class TantivyBackend:
self, self,
document: Document, document: Document,
effective_content: str | None = None, effective_content: str | None = None,
viewer_ids: list[int] | None = None,
) -> tantivy.Document: ) -> tantivy.Document:
"""Build a tantivy Document from a Django Document instance. """Build a tantivy Document from a Django Document instance.
@@ -399,14 +405,10 @@ class TantivyBackend:
doc.add_text("title", document.title) doc.add_text("title", document.title)
doc.add_text("title_sort", document.title) doc.add_text("title_sort", document.title)
doc.add_text("simple_title", document.title) doc.add_text("simple_title", document.title)
doc.add_text("bigram_title", document.title)
doc.add_text("content", content) doc.add_text("content", content)
doc.add_text("bigram_content", content)
doc.add_text("simple_content", content) doc.add_text("simple_content", content)
# Bigram (character-ngram) fields exist for CJK substring search,
# no need to bloat the bigram index with latin characters.
if cjk_title := extract_cjk_text(document.title):
doc.add_text("bigram_title", cjk_title)
if content and (cjk_content := extract_cjk_text(content)):
doc.add_text("bigram_content", cjk_content)
# Original filename - only add if not None/empty # Original filename - only add if not None/empty
if document.original_filename: if document.original_filename:
@@ -416,16 +418,14 @@ class TantivyBackend:
if document.correspondent: if document.correspondent:
doc.add_text("correspondent", document.correspondent.name) doc.add_text("correspondent", document.correspondent.name)
doc.add_text("correspondent_sort", document.correspondent.name) doc.add_text("correspondent_sort", document.correspondent.name)
if cjk_corr := extract_cjk_text(document.correspondent.name): doc.add_text("bigram_correspondent", document.correspondent.name)
doc.add_text("bigram_correspondent", cjk_corr)
doc.add_unsigned("correspondent_id", document.correspondent_id) doc.add_unsigned("correspondent_id", document.correspondent_id)
# Document type # Document type
if document.document_type: if document.document_type:
doc.add_text("document_type", document.document_type.name) doc.add_text("document_type", document.document_type.name)
doc.add_text("type_sort", document.document_type.name) doc.add_text("type_sort", document.document_type.name)
if cjk_type := extract_cjk_text(document.document_type.name): doc.add_text("bigram_document_type", document.document_type.name)
doc.add_text("bigram_document_type", cjk_type)
doc.add_unsigned("document_type_id", document.document_type_id) doc.add_unsigned("document_type_id", document.document_type_id)
# Storage path # Storage path
@@ -437,8 +437,7 @@ class TantivyBackend:
tag_names: list[str] = [] tag_names: list[str] = []
for tag in document.tags.all(): for tag in document.tags.all():
doc.add_text("tag", tag.name) doc.add_text("tag", tag.name)
if cjk_tag := extract_cjk_text(tag.name): doc.add_text("bigram_tag", tag.name)
doc.add_text("bigram_tag", cjk_tag)
doc.add_unsigned("tag_id", tag.pk) doc.add_unsigned("tag_id", tag.pk)
tag_names.append(tag.name) tag_names.append(tag.name)
@@ -493,14 +492,12 @@ class TantivyBackend:
doc.add_unsigned("owner_id", document.owner_id) doc.add_unsigned("owner_id", document.owner_id)
# Viewers with permission # Viewers with permission
if viewer_ids is None: users_with_perms = get_users_with_perms(
users_with_perms = get_users_with_perms( document,
document, only_with_perms_in=["view_document"],
only_with_perms_in=["view_document"], )
) for user in users_with_perms:
viewer_ids = [int(u.id) for u in users_with_perms] doc.add_unsigned("viewer_id", user.pk)
for viewer_id in viewer_ids:
doc.add_unsigned("viewer_id", viewer_id)
# Autocomplete words # Autocomplete words
text_sources = [document.title, content] text_sources = [document.title, content]
@@ -915,8 +912,7 @@ class TantivyBackend:
def rebuild( def rebuild(
self, self,
documents: QuerySet[Document], documents: QuerySet[Document],
iter_wrapper: IterWrapper[tuple[Document, list[int]]] = identity, iter_wrapper: IterWrapper[Document] = identity,
writer_heap_bytes: int = 512_000_000,
) -> None: ) -> None:
""" """
Rebuild the entire search index from scratch. Rebuild the entire search index from scratch.
@@ -927,12 +923,7 @@ class TantivyBackend:
Args: Args:
documents: QuerySet of Document instances to index documents: QuerySet of Document instances to index
iter_wrapper: Optional wrapper function for progress tracking iter_wrapper: Optional wrapper function for progress tracking
(e.g., progress bar). Wraps an iterable of (e.g., progress bar). Should yield each document unchanged.
``(document, viewer_ids)`` pairs and should yield each pair
unchanged, advancing one step per document.
writer_heap_bytes: Tantivy writer memory budget (split across the
writer's threads). Larger values buffer more docs in RAM before
flushing a segment, deferring merge work; they do not avoid it.
""" """
# Create new index (on-disk or in-memory) # Create new index (on-disk or in-memory)
if self._path is not None: if self._path is not None:
@@ -947,17 +938,13 @@ class TantivyBackend:
old_index, old_schema = self._raw_index, self._raw_schema old_index, old_schema = self._raw_index, self._raw_schema
self._raw_index = new_index self._raw_index = new_index
self._raw_schema = new_index.schema self._raw_schema = new_index.schema
# Stream documents one-by-one (so the progress bar advances per
# document) while fetching viewer permissions one SQL query per chunk.
# The stream is Sized, so iter_wrapper can still discover the total.
documents_stream = _DocumentViewerStream(documents, chunk_size=1000)
try: try:
writer = new_index.writer(heap_size=writer_heap_bytes) writer = new_index.writer()
for document, viewer_ids in iter_wrapper(documents_stream): for document in iter_wrapper(documents):
doc = self._build_tantivy_doc( doc = self._build_tantivy_doc(
document, document,
document.get_effective_content(), document.get_effective_content(),
viewer_ids=viewer_ids,
) )
writer.add_document(doc) writer.add_document(doc)
writer.commit() writer.commit()
@@ -972,97 +959,6 @@ class TantivyBackend:
raise raise
def chunked(iterable, size):
iterator = iter(iterable)
while chunk := list(islice(iterator, size)):
yield chunk
class _DocumentViewerStream:
"""Yield (document, viewer_ids) pairs while batch-loading viewer ids.
Viewer permissions are fetched one SQL query per chunk (see
``_bulk_get_viewer_ids``), but documents are yielded individually so a
progress bar wrapped around this stream advances per document rather than
jumping a whole chunk at a time. ``__len__`` lets the progress helper still
discover the total (it inspects ``QuerySet``/``Sized``).
The viewer ids travel with each document in the yielded pair rather than
through a separate mutable attribute, so the pairing survives regardless
of how ``iter_wrapper`` consumes the stream (buffering, batching, etc.)
there is no reliance on the caller advancing this generator in lock-step.
"""
def __init__(self, documents: QuerySet[Document], *, chunk_size: int) -> None:
self._documents = documents
self._chunk_size = chunk_size
def __len__(self) -> int:
return self._documents.count()
def __iter__(self) -> Iterator[tuple[Document, list[int]]]:
# iterator(chunk_size=…) streams from a server-side cursor instead of
# materialising the whole queryset in memory; since Django 4.1 it still
# honours prefetch_related, running the prefetches one batch at a time.
documents = self._documents.iterator(chunk_size=self._chunk_size)
for chunk in chunked(documents, self._chunk_size):
viewer_ids_by_pk = _bulk_get_viewer_ids([doc.pk for doc in chunk])
for doc in chunk:
yield doc, viewer_ids_by_pk.get(doc.pk, [])
def _bulk_get_viewer_ids(doc_pks: Sequence[int]) -> dict[int, list[int]]:
"""Fetch all view_document permissions for a batch of documents in one query.
Mirrors get_users_with_perms(doc, only_with_perms_in=["view_document"])
(with_group_users defaults to True there): a user counts as a viewer if
they hold the permission directly, OR via membership in a group that
holds the permission. Missing the group case would silently drop search
access for group-only viewers.
"""
from collections import defaultdict
from guardian.models import GroupObjectPermission
from guardian.models import UserObjectPermission
from documents.models import Document
# get_for_model is cached by Django, so this costs at most one query total.
ct = ContentType.objects.get_for_model(Document)
str_pks = [str(pk) for pk in doc_pks]
viewer_map: dict[int, set[int]] = defaultdict(set)
# Fold the permission lookup into the query via a join on codename instead
# of a separate Permission.objects.get(), which would otherwise run once per
# chunk during a full reindex.
user_qs = UserObjectPermission.objects.filter(
content_type=ct,
permission__content_type=ct,
permission__codename="view_document",
object_pk__in=str_pks,
).values_list("object_pk", "user_id")
for object_pk, user_id in user_qs:
viewer_map[int(object_pk)].add(user_id)
# User.groups has related_query_name="user", so group__user__id joins
# through the group membership m2m to the member users' ids in the same
# single-query fashion as user_qs above (values_list compiles to one SQL
# JOIN; no per-row Python-side lookups follow, so select_related /
# prefetch_related do not apply here).
group_qs = GroupObjectPermission.objects.filter(
content_type=ct,
permission__content_type=ct,
permission__codename="view_document",
object_pk__in=str_pks,
).values_list("object_pk", "group__user__id")
for object_pk, user_id in group_qs:
if user_id is not None:
viewer_map[int(object_pk)].add(user_id)
return {object_pk: list(user_ids) for object_pk, user_ids in viewer_map.items()}
# Module-level singleton with proper thread safety # Module-level singleton with proper thread safety
_backend: TantivyBackend | None = None _backend: TantivyBackend | None = None
_backend_path: Path | None = None # tracks which INDEX_DIR the singleton uses _backend_path: Path | None = None # tracks which INDEX_DIR the singleton uses
-11
View File
@@ -40,17 +40,6 @@ def _has_cjk(text: str) -> bool:
return bool(_CJK_RE.search(text)) return bool(_CJK_RE.search(text))
def extract_cjk_text(text: str) -> str:
"""Join the CJK runs in ``text`` for indexing into bigram (char-ngram) fields.
Mirrors the query side (``_build_cjk_query``): only CJK runs are ever searched
against the bigram fields, so only CJK runs are worth indexing there. Latin
text fed to a character-bigram field is never matched and only bloats the
index and slows indexing/merge. Returns "" when there is no CJK text.
"""
return " ".join(_CJK_RE.findall(text))
def _build_cjk_query( def _build_cjk_query(
index: tantivy.Index, index: tantivy.Index,
raw_query: str, raw_query: str,
+4 -24
View File
@@ -48,9 +48,6 @@ _LANGUAGE_MAP: dict[str, str] = {
} }
SUPPORTED_LANGUAGES: frozenset[str] = frozenset(_LANGUAGE_MAP) SUPPORTED_LANGUAGES: frozenset[str] = frozenset(_LANGUAGE_MAP)
# Document.title is max_length=128, so use 129 as the limit for
# Tantivy's remove_long filter
_TOKEN_REMOVE_LONG_LIMIT: Final[int] = 129
def register_tokenizers(index: tantivy.Index, language: str | None) -> None: def register_tokenizers(index: tantivy.Index, language: str | None) -> None:
@@ -80,10 +77,10 @@ def register_tokenizers(index: tantivy.Index, language: str | None) -> None:
def _paperless_text(language: str | None) -> tantivy.TextAnalyzer: def _paperless_text(language: str | None) -> tantivy.TextAnalyzer:
"""Main full-text tokenizer for content, title, etc: simple -> remove_long(129) -> lowercase -> ascii_fold [-> stemmer]""" """Main full-text tokenizer for content, title, etc: simple -> remove_long(65) -> lowercase -> ascii_fold [-> stemmer]"""
builder = ( builder = (
tantivy.TextAnalyzerBuilder(tantivy.Tokenizer.simple()) tantivy.TextAnalyzerBuilder(tantivy.Tokenizer.simple())
.filter(tantivy.Filter.remove_long(_TOKEN_REMOVE_LONG_LIMIT)) .filter(tantivy.Filter.remove_long(65))
.filter(tantivy.Filter.lowercase()) .filter(tantivy.Filter.lowercase())
.filter(tantivy.Filter.ascii_fold()) .filter(tantivy.Filter.ascii_fold())
) )
@@ -122,12 +119,12 @@ def _bigram_analyzer() -> tantivy.TextAnalyzer:
def _simple_search_analyzer() -> tantivy.TextAnalyzer: def _simple_search_analyzer() -> tantivy.TextAnalyzer:
"""Tokenizer for simple substring search fields: non-whitespace chunks -> remove_long(129) -> lowercase -> ascii_fold.""" """Tokenizer for simple substring search fields: non-whitespace chunks -> remove_long(65) -> lowercase -> ascii_fold."""
return ( return (
tantivy.TextAnalyzerBuilder( tantivy.TextAnalyzerBuilder(
tantivy.Tokenizer.regex(r"\S+"), tantivy.Tokenizer.regex(r"\S+"),
) )
.filter(tantivy.Filter.remove_long(_TOKEN_REMOVE_LONG_LIMIT)) .filter(tantivy.Filter.remove_long(65))
.filter(tantivy.Filter.lowercase()) .filter(tantivy.Filter.lowercase())
.filter(tantivy.Filter.ascii_fold()) .filter(tantivy.Filter.ascii_fold())
.build() .build()
@@ -154,23 +151,6 @@ def simple_search_tokens(text: str) -> list[str]:
return _SIMPLE_SEARCH_ANALYZER.analyze(text) return _SIMPLE_SEARCH_ANALYZER.analyze(text)
# Autocomplete word extraction: tokenize -> lowercase -> ascii_fold in a single
# Rust pass. Uses the simple tokenizer so extracted words match how document
# content is actually indexed (the content tokenizer _paperless_text also uses
# simple()), replacing a Python regex scan plus per-token folding.
_AUTOCOMPLETE_ANALYZER: Final = (
tantivy.TextAnalyzerBuilder(tantivy.Tokenizer.simple())
.filter(tantivy.Filter.lowercase())
.filter(tantivy.Filter.ascii_fold())
.build()
)
def autocomplete_tokens(text: str) -> list[str]:
"""Tokenize text into normalized autocomplete words (lowercased, ascii-folded)."""
return _AUTOCOMPLETE_ANALYZER.analyze(text)
def ascii_fold(text: str) -> str: def ascii_fold(text: str) -> str:
"""Fold text to ASCII using the same mapping as the content tokenizers. """Fold text to ASCII using the same mapping as the content tokenizers.
+1 -6
View File
@@ -163,12 +163,7 @@ def scan(query: str) -> list[Token]:
i += 1 i += 1
continue continue
token, i = matched token, i = matched
if buf and buf[-1] == ",": _flush(buf, tokens)
buf.pop()
_flush(buf, tokens)
tokens.append(Comma())
else:
_flush(buf, tokens)
tokens.append(token) tokens.append(token)
i = _maybe_comma(query, i, tokens) i = _maybe_comma(query, i, tokens)
_flush(buf, tokens) _flush(buf, tokens)
+4 -67
View File
@@ -1,7 +1,5 @@
import pytest import pytest
from django.contrib.auth.models import Group
from django.contrib.auth.models import User from django.contrib.auth.models import User
from guardian.shortcuts import assign_perm
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from documents.models import CustomField from documents.models import CustomField
@@ -17,7 +15,6 @@ from documents.tests.factories import CorrespondentFactory
from documents.tests.factories import DocumentFactory from documents.tests.factories import DocumentFactory
from documents.tests.factories import DocumentTypeFactory from documents.tests.factories import DocumentTypeFactory
from documents.tests.factories import TagFactory from documents.tests.factories import TagFactory
from documents.tests.factories import UserFactory
pytestmark = [pytest.mark.search, pytest.mark.django_db] pytestmark = [pytest.mark.search, pytest.mark.django_db]
@@ -264,36 +261,6 @@ class TestSearch:
== 1 == 1
) )
@pytest.mark.parametrize(
("search_mode", "query"),
[
pytest.param(SearchMode.TITLE, "12345", id="title_search"),
pytest.param(SearchMode.TEXT, "12345", id="text_search"),
pytest.param(SearchMode.QUERY, None, id="query_title_exact"),
],
)
def test_search_modes_match_model_limit_title_tokens(
self,
backend: TantivyBackend,
search_mode: SearchMode,
query: str | None,
) -> None:
"""Search must keep filename-like title tokens up to the model limit."""
long_title = "1234567890" * 12 + "12345678"
doc = Document.objects.create(
title=long_title,
content="ordinary content",
checksum="TXT12",
pk=18,
)
backend.add_or_update(doc)
assert backend.search_ids(
query or f"title:{long_title}",
user=None,
search_mode=search_mode,
) == [doc.pk]
@pytest.mark.parametrize( @pytest.mark.parametrize(
("mode", "title", "content", "hits", "misses"), ("mode", "title", "content", "hits", "misses"),
[ [
@@ -564,48 +531,18 @@ class TestRebuild:
"""Test index rebuilding functionality.""" """Test index rebuilding functionality."""
def test_with_iter_wrapper_called(self, backend: TantivyBackend) -> None: def test_with_iter_wrapper_called(self, backend: TantivyBackend) -> None:
"""Index rebuild must pass (document, viewer_ids) pairs through iter_wrapper.""" """Index rebuild must pass documents through iter_wrapper for progress tracking."""
seen = [] seen = []
def wrapper(pairs): def wrapper(docs):
for doc, viewer_ids in pairs: for doc in docs:
seen.append(doc.pk) seen.append(doc.pk)
yield doc, viewer_ids yield doc
Document.objects.create(title="Tracked", content="x", checksum="TW1", pk=30) Document.objects.create(title="Tracked", content="x", checksum="TW1", pk=30)
backend.rebuild(Document.objects.all(), iter_wrapper=wrapper) backend.rebuild(Document.objects.all(), iter_wrapper=wrapper)
assert 30 in seen assert 30 in seen
def test_includes_group_granted_viewers(self, backend: TantivyBackend) -> None:
"""Rebuild must index viewer ids for group-only grants, not just direct ones.
The batched viewer-id lookup used during rebuild() must mirror
get_users_with_perms(with_group_users=True), which is the default
used by the non-batched per-document indexing path. Without it, a
user who can only see a document via group membership would lose
search access to it after any full reindex.
"""
owner = UserFactory()
group_member = UserFactory()
group = Group.objects.create(name="viewers")
group_member.groups.add(group)
doc = DocumentFactory(
title="Group shared doc",
content="group secret keyword",
owner=owner,
)
assign_perm("view_document", group, doc)
backend.rebuild(Document.objects.all())
ids = backend.search_ids(
"group secret",
user=group_member,
search_mode=SearchMode.QUERY,
)
assert ids == [doc.pk]
class TestAutocomplete: class TestAutocomplete:
"""Test autocomplete functionality.""" """Test autocomplete functionality."""
@@ -99,25 +99,6 @@ class TestTokenizers:
) )
assert simple_search_index.searcher().search(q, limit=5).count == 1 assert simple_search_index.searcher().search(q, limit=5).count == 1
def test_simple_search_analyzer_supports_model_limit_token_substrings(
self,
simple_search_index: tantivy.Index,
) -> None:
"""Simple substring search keeps tokens up to Document.title's model limit."""
long_token = "abcdefghij" * 12 + "abcdefgh"
writer = simple_search_index.writer()
doc = tantivy.Document()
doc.add_text("simple_content", long_token)
writer.add_document(doc)
writer.commit()
simple_search_index.reload()
q = tantivy.Query.regex_query(
simple_search_index.schema,
"simple_content",
".*cdefg.*",
)
assert simple_search_index.searcher().search(q, limit=5).count == 1
def test_unsupported_language_logs_warning(self, caplog: LogCaptureFixture) -> None: def test_unsupported_language_logs_warning(self, caplog: LogCaptureFixture) -> None:
"""Unsupported language codes should log a warning and disable stemming gracefully.""" """Unsupported language codes should log a warning and disable stemming gracefully."""
sb = tantivy.SchemaBuilder() sb = tantivy.SchemaBuilder()
@@ -716,12 +716,6 @@ class TestISODatetimeBounds:
"added:[2026-05-01T00:00:00Z TO 2026-06-01T00:00:00Z]" "added:[2026-05-01T00:00:00Z TO 2026-06-01T00:00:00Z]"
) )
def test_translate_query_text_before_comma_separated_date_clause(self) -> None:
result = translate_query("schäfersee,created:previous year", UTC)
assert result == (
"schäfersee AND created:[2025-01-01T00:00:00Z TO 2026-01-01T00:00:00Z]"
)
def test_invalid_iso_datetime_raises(self) -> None: def test_invalid_iso_datetime_raises(self) -> None:
# A token with "T" that is not valid ISO datetime -> raise. # A token with "T" that is not valid ISO datetime -> raise.
with pytest.raises(InvalidDateQuery) as exc_info: with pytest.raises(InvalidDateQuery) as exc_info:
@@ -138,7 +138,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
title = str(kwargs) title = str(kwargs)
document = Document.objects.create( document = Document.objects.create(
title=title, title=title,
checksum=title[:64], checksum=title,
archive_serial_number=len(self.documents) + 1, archive_serial_number=len(self.documents) + 1,
) )
data = { data = {
-29
View File
@@ -176,35 +176,6 @@ class TestMakeIndex:
call_command("document_index", "reindex", if_needed=True, skip_checks=True) call_command("document_index", "reindex", if_needed=True, skip_checks=True)
mock_get_backend.return_value.rebuild.assert_called_once() mock_get_backend.return_value.rebuild.assert_called_once()
def test_reindex_default_heap_size_not_overridden(
self,
mocker: MockerFixture,
) -> None:
"""Without --heap-size-mb, rebuild must use its own default heap size."""
mock_get_backend = mocker.patch(
"documents.management.commands.document_index.get_backend",
)
call_command("document_index", "reindex", skip_checks=True)
_, kwargs = mock_get_backend.return_value.rebuild.call_args
assert "writer_heap_bytes" not in kwargs
def test_reindex_heap_size_mb_passed_to_rebuild(
self,
mocker: MockerFixture,
) -> None:
"""--heap-size-mb must convert to bytes and pass through to rebuild."""
mock_get_backend = mocker.patch(
"documents.management.commands.document_index.get_backend",
)
call_command(
"document_index",
"reindex",
heap_size_mb=128,
skip_checks=True,
)
_, kwargs = mock_get_backend.return_value.rebuild.call_args
assert kwargs["writer_heap_bytes"] == 128_000_000
@pytest.mark.management @pytest.mark.management
class TestRenamer(DirectoriesMixin, FileSystemAssertsMixin, TestCase): class TestRenamer(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
+2 -9
View File
@@ -3769,20 +3769,13 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
validated_data=serializer.validated_data, validated_data=serializer.validated_data,
) )
documents = Document.objects.filter(pk__in=ids) documents = Document.objects.filter(pk__in=ids)
versioned_documents = []
compression = serializer.validated_data.get("compression") compression = serializer.validated_data.get("compression")
content = serializer.validated_data.get("content") content = serializer.validated_data.get("content")
follow_filename_format = serializer.validated_data.get("follow_formatting") follow_filename_format = serializer.validated_data.get("follow_formatting")
for document in documents: for document in documents:
root_doc = get_root_document(document) if not has_perms_owner_aware(request.user, "change_document", document):
if not has_perms_owner_aware(request.user, "view_document", root_doc):
return HttpResponseForbidden("Insufficient permissions") return HttpResponseForbidden("Insufficient permissions")
versioned_documents.append(
get_latest_version_for_root(
root_doc,
),
)
if content == "both": if content == "both":
strategy_class = OriginalAndArchiveStrategy strategy_class = OriginalAndArchiveStrategy
@@ -3805,7 +3798,7 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
zipf, zipf,
follow_formatting=follow_filename_format, follow_formatting=follow_filename_format,
) )
for document in versioned_documents: for document in documents:
strategy.add_document(document) strategy.add_document(document)
f = temp_path.open("rb") f = temp_path.open("rb")
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More