mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-19 18:34:56 +00:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d863a553a | ||
|
|
c753091a44 | ||
|
|
71557d7c64 | ||
|
|
66f5111dc1 | ||
|
|
0addb448f5 | ||
|
|
5428ea5caa | ||
|
|
2de2dfdb81 | ||
|
|
f0d934259c | ||
|
|
735c3b073a | ||
|
|
e2cf3633fe | ||
|
|
c0589a9ce9 | ||
|
|
2135572adb | ||
|
|
697e262b5d | ||
|
|
5a65c4448d | ||
|
|
370a0a29ba | ||
|
|
536007f42b | ||
|
|
71e2dd4b08 | ||
|
|
dabd7826d0 | ||
|
|
054b234739 | ||
|
|
77dd4d1b0c | ||
|
|
56dc3e426e | ||
|
|
19960cc4fa | ||
|
|
e145fe8cc7 | ||
|
|
fce55a1609 | ||
|
|
1dabd2601d | ||
|
|
88f69841b3 | ||
|
|
5b8fbdcec7 | ||
|
|
beb048b94a | ||
|
|
b33d11778a |
@@ -38,7 +38,7 @@ repos:
|
||||
- json
|
||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 'v3.8.4'
|
||||
rev: 'v3.9.4'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
@@ -46,16 +46,16 @@ repos:
|
||||
- ts
|
||||
- markdown
|
||||
additional_dependencies:
|
||||
- prettier@3.8.3
|
||||
- prettier@3.9.4
|
||||
- 'prettier-plugin-organize-imports@4.3.0'
|
||||
# Python hooks
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.17
|
||||
rev: v0.15.20
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "v2.24.1"
|
||||
rev: "v2.25.1"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
additional_dependencies: [tomli]
|
||||
|
||||
+5
-1
@@ -30,7 +30,7 @@ RUN set -eux \
|
||||
# Purpose: Installs s6-overlay and rootfs
|
||||
# Comments:
|
||||
# - Don't leave anything extra in here either
|
||||
FROM ghcr.io/astral-sh/uv:0.11.19-python3.12-trixie-slim AS s6-overlay-base
|
||||
FROM ghcr.io/astral-sh/uv:0.11.28-python3.12-trixie-slim AS s6-overlay-base
|
||||
|
||||
WORKDIR /usr/src/s6
|
||||
|
||||
@@ -238,6 +238,10 @@ RUN set -eux \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \
|
||||
&& echo "Making fontconfig cache writable for arbitrary container UIDs" \
|
||||
&& 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" \
|
||||
&& 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 \
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# correct networking for the tests
|
||||
services:
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
hostname: gotenberg
|
||||
container_name: gotenberg
|
||||
network_mode: host
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
greenmail:
|
||||
image: docker.io/greenmail/standalone:2.1.8
|
||||
image: docker.io/greenmail/standalone:2.1.9
|
||||
hostname: greenmail
|
||||
container_name: greenmail
|
||||
environment:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
- "3143:3143" # IMAP
|
||||
restart: unless-stopped
|
||||
nginx:
|
||||
image: docker.io/nginx:1.31.1-alpine
|
||||
image: docker.io/nginx:1.31.2-alpine
|
||||
hostname: nginx
|
||||
container_name: nginx
|
||||
ports:
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# 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_ENDPOINT: http://tika:9998
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# 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_ENDPOINT: http://tika:9998
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -326,3 +326,11 @@ behind a reverse proxy may need to set
|
||||
[`PAPERLESS_TRUSTED_PROXIES`](configuration.md#PAPERLESS_TRUSTED_PROXIES),
|
||||
[`PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER`](configuration.md#PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER),
|
||||
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
@@ -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`.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```yaml
|
||||
webserver:
|
||||
|
||||
+2
-2
@@ -101,11 +101,11 @@ dev = [
|
||||
{ include-group = "testing" },
|
||||
]
|
||||
docs = [
|
||||
"zensical>=0.0.43",
|
||||
"zensical>=0.0.47",
|
||||
]
|
||||
lint = [
|
||||
"prek~=0.3.10",
|
||||
"ruff~=0.15.15",
|
||||
"ruff~=0.15.20",
|
||||
]
|
||||
testing = [
|
||||
"daphne",
|
||||
|
||||
@@ -79,6 +79,16 @@
|
||||
"glob": "{pdf.worker.min.mjs,pdf.min.mjs}",
|
||||
"input": "node_modules/pdfjs-dist/legacy/build/",
|
||||
"output": "/assets/js/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/pdfjs-dist/wasm/",
|
||||
"output": "/assets/wasm/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/pdfjs-dist/iccs/",
|
||||
"output": "/assets/iccs/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
|
||||
+116
-87
@@ -332,7 +332,7 @@
|
||||
</context-group>
|
||||
<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">85</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
</context-group>
|
||||
<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>
|
||||
@@ -379,11 +379,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">272</context>
|
||||
<context context-type="linenumber">273</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">274</context>
|
||||
<context context-type="linenumber">275</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5890330709052835856" datatype="html">
|
||||
@@ -724,11 +724,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">307</context>
|
||||
<context context-type="linenumber">308</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">310</context>
|
||||
<context context-type="linenumber">311</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2272120016352772836" datatype="html">
|
||||
@@ -757,14 +757,14 @@
|
||||
<source>lines</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8838884664569764142" datatype="html">
|
||||
<source>Auto refresh</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
<context context-type="linenumber">22</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
@@ -775,11 +775,11 @@
|
||||
<source>Loading...</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">48</context>
|
||||
<context context-type="linenumber">49</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
@@ -886,7 +886,7 @@
|
||||
<source>Jump to bottom</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1255048712725285892" datatype="html">
|
||||
@@ -1135,11 +1135,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">232</context>
|
||||
<context context-type="linenumber">233</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">234</context>
|
||||
<context context-type="linenumber">235</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
|
||||
@@ -1696,7 +1696,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context>
|
||||
@@ -1801,11 +1801,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">295</context>
|
||||
<context context-type="linenumber">296</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">297</context>
|
||||
<context context-type="linenumber">298</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8492095365580052820" datatype="html">
|
||||
@@ -1905,11 +1905,18 @@
|
||||
<context context-type="linenumber">227</context>
|
||||
</context-group>
|
||||
</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">
|
||||
<source>Reset filters</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">104</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@@ -1924,14 +1931,14 @@
|
||||
<source>{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">125</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8953033926734869941" datatype="html">
|
||||
<source>Name</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">147</context>
|
||||
<context context-type="linenumber">148</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
@@ -2042,7 +2049,7 @@
|
||||
<source>Created</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">148</context>
|
||||
<context context-type="linenumber">149</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||
@@ -2073,21 +2080,21 @@
|
||||
<source>Results</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">150</context>
|
||||
<context context-type="linenumber">151</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="314315645942131479" datatype="html">
|
||||
<source>Info</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">152</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3193976279273491157" datatype="html">
|
||||
<source>Actions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
<context context-type="linenumber">154</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
@@ -2158,14 +2165,14 @@
|
||||
<source>click for full output</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1536087519743707362" datatype="html">
|
||||
<source>Dismiss</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">217</context>
|
||||
<context context-type="linenumber">218</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
@@ -2181,35 +2188,35 @@
|
||||
</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">50</context>
|
||||
<context context-type="linenumber">51</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2134950584701094962" datatype="html">
|
||||
<source>Open Document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">222</context>
|
||||
<context context-type="linenumber">223</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5404759957685833020" datatype="html">
|
||||
<source>Result message</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">235</context>
|
||||
<context context-type="linenumber">236</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6621329748219109148" datatype="html">
|
||||
<source>Duplicate</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">242</context>
|
||||
<context context-type="linenumber">243</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7593555694782789615" datatype="html">
|
||||
<source>Open</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">250</context>
|
||||
<context context-type="linenumber">251</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
|
||||
@@ -2240,21 +2247,21 @@
|
||||
<source>Input data</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">260</context>
|
||||
<context context-type="linenumber">261</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1585185618099050920" datatype="html">
|
||||
<source>Result data</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">266</context>
|
||||
<context context-type="linenumber">267</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7976920528153858271" datatype="html">
|
||||
<source>No tasks match the current filters.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">288</context>
|
||||
<context context-type="linenumber">289</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2525230676386818985" datatype="html">
|
||||
@@ -2513,11 +2520,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">255</context>
|
||||
<context context-type="linenumber">256</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">258</context>
|
||||
<context context-type="linenumber">259</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3818027200170621545" datatype="html">
|
||||
@@ -2870,11 +2877,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">286</context>
|
||||
<context context-type="linenumber">287</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">288</context>
|
||||
<context context-type="linenumber">289</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4569276013106377105" datatype="html">
|
||||
@@ -3211,11 +3218,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">316</context>
|
||||
<context context-type="linenumber">317</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">319</context>
|
||||
<context context-type="linenumber">320</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="472206565520537964" datatype="html">
|
||||
@@ -3240,36 +3247,36 @@
|
||||
<source>Close all</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">164</context>
|
||||
<context context-type="linenumber">165</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3897348120591552265" datatype="html">
|
||||
<source>Manage</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">173</context>
|
||||
<context context-type="linenumber">174</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8008131619909556709" datatype="html">
|
||||
<source>Attributes</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">180</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7437910965833684826" datatype="html">
|
||||
<source>Correspondents</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">208</context>
|
||||
<context context-type="linenumber">209</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
@@ -3288,7 +3295,7 @@
|
||||
<source>Document types</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">213</context>
|
||||
<context context-type="linenumber">214</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
|
||||
@@ -3299,7 +3306,7 @@
|
||||
<source>Storage paths</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">218</context>
|
||||
<context context-type="linenumber">219</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
|
||||
@@ -3310,7 +3317,7 @@
|
||||
<source>Custom fields</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">223</context>
|
||||
<context context-type="linenumber">224</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
@@ -3329,11 +3336,11 @@
|
||||
<source>Workflows</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">241</context>
|
||||
<context context-type="linenumber">242</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">243</context>
|
||||
<context context-type="linenumber">244</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
|
||||
@@ -3344,71 +3351,71 @@
|
||||
<source>Mail</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">248</context>
|
||||
<context context-type="linenumber">249</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">251</context>
|
||||
<context context-type="linenumber">252</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7844706011418789951" datatype="html">
|
||||
<source>Administration</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">266</context>
|
||||
<context context-type="linenumber">267</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3008420115644088420" datatype="html">
|
||||
<source>Configuration</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">279</context>
|
||||
<context context-type="linenumber">280</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">281</context>
|
||||
<context context-type="linenumber">282</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1534029177398918729" datatype="html">
|
||||
<source>GitHub</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">326</context>
|
||||
<context context-type="linenumber">327</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4112664765954374539" datatype="html">
|
||||
<source>is available.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">335,336</context>
|
||||
<context context-type="linenumber">336,337</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1175891574282637937" datatype="html">
|
||||
<source>Click to view.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">336</context>
|
||||
<context context-type="linenumber">337</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9811291095862612" datatype="html">
|
||||
<source>Paperless-ngx can automatically check for updates</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">340</context>
|
||||
<context context-type="linenumber">341</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="894819944961861800" datatype="html">
|
||||
<source> How does this work? </source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">347,349</context>
|
||||
<context context-type="linenumber">348,350</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="509090351011426949" datatype="html">
|
||||
<source>Update available</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">360</context>
|
||||
<context context-type="linenumber">361</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1542489069631984294" datatype="html">
|
||||
@@ -3604,6 +3611,17 @@
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
</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">
|
||||
<source>Ask a question about this document...</source>
|
||||
<context-group purpose="location">
|
||||
@@ -5828,13 +5846,6 @@
|
||||
<context context-type="linenumber">32</context>
|
||||
</context-group>
|
||||
</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">
|
||||
<source>Use archive version</source>
|
||||
<context-group purpose="location">
|
||||
@@ -6694,7 +6705,7 @@
|
||||
</context-group>
|
||||
<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">87</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
<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>
|
||||
@@ -6771,18 +6782,25 @@
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
</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">
|
||||
<source>Copy 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">81</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8372007266188249803" datatype="html">
|
||||
<source>Never</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">93</context>
|
||||
<context context-type="linenumber">94</context>
|
||||
</context-group>
|
||||
<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>
|
||||
@@ -6797,7 +6815,7 @@
|
||||
<source>File version</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">96</context>
|
||||
<context context-type="linenumber">97</context>
|
||||
</context-group>
|
||||
<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>
|
||||
@@ -6808,7 +6826,7 @@
|
||||
<source>Size</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">99</context>
|
||||
<context context-type="linenumber">100</context>
|
||||
</context-group>
|
||||
<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>
|
||||
@@ -6819,14 +6837,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>
|
||||
<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">109</context>
|
||||
<context context-type="linenumber">110</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7274497464693086242" datatype="html">
|
||||
<source>Manage share link bundles</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">113</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
@@ -7817,8 +7835,8 @@
|
||||
<context context-type="linenumber">314</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7985558498848210210" datatype="html">
|
||||
<source>Original MD5 checksum</source>
|
||||
<trans-unit id="2659735245739197634" datatype="html">
|
||||
<source>Original SHA256 checksum</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
<context context-type="linenumber">318</context>
|
||||
@@ -7838,8 +7856,8 @@
|
||||
<context context-type="linenumber">326</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="342875990758166588" datatype="html">
|
||||
<source>Archive MD5 checksum</source>
|
||||
<trans-unit id="6714358112223607756" datatype="html">
|
||||
<source>Archive SHA256 checksum</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
<context context-type="linenumber">331</context>
|
||||
@@ -8237,63 +8255,67 @@
|
||||
<source>Optional</source>
|
||||
<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">16</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="787787001194154763" datatype="html">
|
||||
<source>Add new version</source>
|
||||
<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">31</context>
|
||||
<context context-type="linenumber">32</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3119565892291077820" datatype="html">
|
||||
<source>Uploading version...</source>
|
||||
<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">38</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8538204518127762311" datatype="html">
|
||||
<source>Processing version...</source>
|
||||
<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">44</context>
|
||||
<context context-type="linenumber">45</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6587183611950398104" datatype="html">
|
||||
<source>Version upload failed.</source>
|
||||
<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">49</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6328986243040954523" datatype="html">
|
||||
<source>Version label</source>
|
||||
<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">80</context>
|
||||
<context context-type="linenumber">75</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>
|
||||
</trans-unit>
|
||||
<trans-unit id="2724055831234181057" datatype="html">
|
||||
<source>Version</source>
|
||||
<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">91</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2491751767883178298" datatype="html">
|
||||
<source>Delete this version?</source>
|
||||
<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">122</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="894108684749537200" datatype="html">
|
||||
<source>Delete version</source>
|
||||
<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">127</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9111284310237278846" datatype="html">
|
||||
@@ -10196,6 +10218,13 @@
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
</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">
|
||||
<source>Received</source>
|
||||
<context-group purpose="location">
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
i18n-info>
|
||||
<div class="input-group input-group-sm align-items-center">
|
||||
<div class="input-group input-group-sm me-3">
|
||||
<span class="input-group-text text-muted" i18n>Show</span>
|
||||
<label class="input-group-text text-muted" for="logLineLimit" i18n>Show</label>
|
||||
<input
|
||||
id="logLineLimit"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="100"
|
||||
@@ -17,7 +18,7 @@
|
||||
<span class="input-group-text text-muted" i18n>lines</span>
|
||||
</div>
|
||||
<div class="form-check form-switch mt-1">
|
||||
<input class="form-check-input" type="checkbox" role="switch" [ngModel]="autoRefreshEnabled()" (ngModelChange)="autoRefreshEnabled.set($event)">
|
||||
<input id="autoRefreshSwitch" 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,9 +100,7 @@ export class LogsComponent
|
||||
parsed.some((log, idx) => {
|
||||
const current = this.logs()[idx]
|
||||
return (
|
||||
!current ||
|
||||
current.message !== log.message ||
|
||||
current.level !== log.level
|
||||
current?.message !== log.message || current?.level !== log.level
|
||||
)
|
||||
})
|
||||
if (hasChanges) {
|
||||
|
||||
@@ -150,10 +150,10 @@
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Full search links to</span>
|
||||
<label for="searchLink" i18n>Full search links to</label>
|
||||
</div>
|
||||
<div class="col mb-3">
|
||||
<select class="form-select" formControlName="searchLink">
|
||||
<select id="searchLink" class="form-select" formControlName="searchLink">
|
||||
<option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option>
|
||||
<option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option>
|
||||
</select>
|
||||
@@ -199,10 +199,10 @@
|
||||
<h5 i18n>Documents</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Items per page</span>
|
||||
<label for="documentListItemPerPage" i18n>Items per page</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select class="form-select" formControlName="documentListItemPerPage">
|
||||
<select id="documentListItemPerPage" class="form-select" formControlName="documentListItemPerPage">
|
||||
<option [ngValue]="10">10</option>
|
||||
<option [ngValue]="25">25</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
@@ -274,10 +274,10 @@
|
||||
<h5 class="mt-3" i18n>PDF Editor</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default editing mode</span>
|
||||
<label for="pdfEditorDefaultEditMode" i18n>Default editing mode</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select class="form-select" formControlName="pdfEditorDefaultEditMode">
|
||||
<select id="pdfEditorDefaultEditMode" class="form-select" formControlName="pdfEditorDefaultEditMode">
|
||||
<option [ngValue]="PdfEditorEditMode.Create" i18n>Create new document(s)</option>
|
||||
<option [ngValue]="PdfEditorEditMode.Update" i18n>Add document version</option>
|
||||
</select>
|
||||
|
||||
@@ -399,17 +399,17 @@ describe('SettingsComponent', () => {
|
||||
completeSetup()
|
||||
const field = 'storage_path'
|
||||
expect(
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value.length
|
||||
).toEqual(0)
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value
|
||||
).toHaveLength(0)
|
||||
component.toggleDocumentDetailField(field, false)
|
||||
expect(
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value.length
|
||||
).toEqual(1)
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value
|
||||
).toHaveLength(1)
|
||||
expect(component.isDocumentDetailFieldShown(field)).toBeFalsy()
|
||||
component.toggleDocumentDetailField(field, true)
|
||||
expect(
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value.length
|
||||
).toEqual(0)
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value
|
||||
).toHaveLength(0)
|
||||
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>
|
||||
</button>
|
||||
<div class="form-check form-switch mb-0 ms-2">
|
||||
<input class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
|
||||
<input id="autoRefreshSwitch" class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
|
||||
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,7 +93,8 @@
|
||||
<i-bs width="1em" height="1em" name="x"></i-bs>
|
||||
</button>
|
||||
}
|
||||
<input #filterInput class="form-control form-control-sm" type="text"
|
||||
<label class="visually-hidden" for="taskSearch" i18n>Search tasks</label>
|
||||
<input #filterInput id="taskSearch" class="form-control form-control-sm" type="text"
|
||||
(keyup)="filterInputKeyup($event)"
|
||||
[(ngModel)]="filterText">
|
||||
</div>
|
||||
@@ -211,7 +212,7 @@
|
||||
<i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs>
|
||||
</button>
|
||||
</td>
|
||||
<td scope="row" class="actions-col">
|
||||
<td class="actions-col">
|
||||
<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 }">
|
||||
<i-bs name="check" class="me-1"></i-bs><ng-container i18n>Dismiss</ng-container>
|
||||
|
||||
@@ -150,7 +150,8 @@
|
||||
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
|
||||
popoverClass="popover-slim">
|
||||
<i-bs class="me-2" name="file-text"></i-bs><span>{{d.title | documentTitle}}</span>
|
||||
<span class="close flex-column justify-content-center" (click)="closeDocument(d); $event.preventDefault()">
|
||||
<span class="close flex-column justify-content-center"
|
||||
(click)="$event.preventDefault(); $event.stopPropagation(); closeDocument(d)">
|
||||
<i-bs name="x"></i-bs>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
</div>
|
||||
|
||||
<form class="chat-input">
|
||||
<label class="visually-hidden" for="chatInput" i18n>Message</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
#chatInput
|
||||
id="chatInput"
|
||||
class="form-control form-control-sm" name="chatInput" type="text"
|
||||
[placeholder]="placeholder"
|
||||
[disabled]="loading"
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('ChatComponent', () => {
|
||||
component.input = 'Hello'
|
||||
component.sendMessage()
|
||||
|
||||
expect(component.messages.length).toBe(2)
|
||||
expect(component.messages).toHaveLength(2)
|
||||
expect(component.messages[0].content).toBe('Hello')
|
||||
expect(component.loading).toBe(true)
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('ChatComponent', () => {
|
||||
const message = { content: '', role: 'assistant', isStreaming: true }
|
||||
component.enqueueTypewriter(null, message as any) // coverage for null
|
||||
component.enqueueTypewriter('Hello', message as any)
|
||||
expect(component['typewriterBuffer'].length).toBe(4)
|
||||
expect(component['typewriterBuffer']).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('should scroll to bottom after sending a message', () => {
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@
|
||||
<div class="col">
|
||||
<div class="trigger-filters mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<label class="form-label mb-0" i18n>Advanced Filters</label>
|
||||
<span class="form-label mb-0" i18n>Advanced Filters</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary ms-auto"
|
||||
|
||||
+5
-5
@@ -401,7 +401,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
component.addFilter(triggerGroup as FormGroup)
|
||||
|
||||
const filters = component.getFiltersFormArray(triggerGroup as FormGroup)
|
||||
expect(filters.length).toBe(3)
|
||||
expect(filters).toHaveLength(3)
|
||||
|
||||
filters.at(0).get('values').setValue([1])
|
||||
filters.at(1).get('values').setValue([2, 3])
|
||||
@@ -672,7 +672,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
} as any
|
||||
|
||||
const filters = component['buildFiltersFormArray'](trigger)
|
||||
expect(filters.length).toBe(0)
|
||||
expect(filters).toHaveLength(0)
|
||||
|
||||
component.filterDefinitions = originalDefinitions
|
||||
})
|
||||
@@ -739,7 +739,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
component.ngOnInit()
|
||||
const triggerGroup = component.triggerFields.at(0) as FormGroup
|
||||
const filters = component.getFiltersFormArray(triggerGroup)
|
||||
expect(filters.length).toBe(13)
|
||||
expect(filters).toHaveLength(13)
|
||||
const customFieldFilter = filters.at(12) as FormGroup
|
||||
expect(customFieldFilter.get('type').value).toBe(
|
||||
TriggerFilterType.CustomFieldQuery
|
||||
@@ -969,12 +969,12 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
component.addFilter(triggerGroup)
|
||||
|
||||
component.removeFilter(triggerGroup, 0)
|
||||
expect(component.getFiltersFormArray(triggerGroup).length).toBe(0)
|
||||
expect(component.getFiltersFormArray(triggerGroup)).toHaveLength(0)
|
||||
|
||||
component.addFilter(triggerGroup)
|
||||
const filterArrayAfterAdd = component.getFiltersFormArray(triggerGroup)
|
||||
filterArrayAfterAdd.at(0).get('type').setValue(TriggerFilterType.TagsAll)
|
||||
expect(component.getFiltersFormArray(triggerGroup).length).toBe(1)
|
||||
expect(component.getFiltersFormArray(triggerGroup)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should remove selected custom field from the form group', () => {
|
||||
|
||||
+1
-1
@@ -933,7 +933,7 @@ export class WorkflowEditDialogComponent
|
||||
|
||||
getFilterSelectItems(type: TriggerFilterType) {
|
||||
const definition = this.getFilterDefinition(type)
|
||||
if (!definition || definition.inputType !== 'select') {
|
||||
if (definition?.inputType !== 'select') {
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -157,8 +157,8 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
private removeChildren(tagIDs: number[], tag: Tag) {
|
||||
if (tag.children?.length) {
|
||||
const childIDs = tag.children.map((child) => child.id)
|
||||
tagIDs = tagIDs.filter((id) => !childIDs.includes(id))
|
||||
const childIDs = new Set(tag.children.map((child) => child.id))
|
||||
tagIDs = tagIDs.filter((id) => !childIDs.has(id))
|
||||
for (const child of tag.children) {
|
||||
tagIDs = this.removeChildren(tagIDs, child)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { environment } from 'src/environments/environment'
|
||||
})
|
||||
export class PageHeaderComponent {
|
||||
private titleService = inject(Title)
|
||||
private clipboard = inject(Clipboard)
|
||||
private readonly clipboard = inject(Clipboard)
|
||||
|
||||
readonly id = input<number>(undefined)
|
||||
readonly subTitle = input('')
|
||||
|
||||
@@ -49,8 +49,7 @@ describe('PngxPdfViewerComponent', () => {
|
||||
new URL('assets/js/pdf.worker.min.mjs', document.baseURI).toString()
|
||||
)
|
||||
const isVisible = (component as any).findController.onIsPageVisible as
|
||||
| (() => boolean)
|
||||
| undefined
|
||||
(() => boolean) | undefined
|
||||
expect(isVisible?.()).toBe(true)
|
||||
expect(loadSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ numPages: 1 })
|
||||
@@ -61,6 +60,7 @@ describe('PngxPdfViewerComponent', () => {
|
||||
|
||||
it('resolves the worker source relative to the document base URI', async () => {
|
||||
setBaseHref('/paperless/')
|
||||
const getDocumentSpy = jest.spyOn(pdfjs, 'getDocument')
|
||||
|
||||
await initComponent()
|
||||
|
||||
@@ -70,6 +70,13 @@ describe('PngxPdfViewerComponent', () => {
|
||||
expect(pdfjs.GlobalWorkerOptions.workerSrc).toContain(
|
||||
'/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 () => {
|
||||
|
||||
@@ -174,10 +174,12 @@ export class PngxPdfViewerComponent
|
||||
'assets/js/pdf.worker.min.mjs',
|
||||
this.document.baseURI
|
||||
).toString()
|
||||
let initOptions = {
|
||||
const initOptions = {
|
||||
url: this.src,
|
||||
password: this.password,
|
||||
withCredentials: true,
|
||||
wasmUrl: new URL('assets/wasm/', this.document.baseURI).toString(),
|
||||
iccUrl: new URL('assets/iccs/', this.document.baseURI).toString(),
|
||||
}
|
||||
this.loadingTask = getDocument(initOptions)
|
||||
try {
|
||||
|
||||
+2
-1
@@ -65,8 +65,9 @@
|
||||
<dd class="col-sm-8"><code>{{ createdBundle.slug }}</code></dd>
|
||||
<dt class="col-sm-4" i18n>Link</dt>
|
||||
<dd class="col-sm-8">
|
||||
<label class="visually-hidden" for="shareBundleLink" i18n>Share link</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
|
||||
<input id="shareBundleLink" class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
type="button"
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
<td>{{metadata()?.original_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original MD5 checksum</td>
|
||||
<td i18n>Original SHA256 checksum</td>
|
||||
<td>{{metadata()?.original_checksum}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -328,7 +328,7 @@
|
||||
</tr>
|
||||
@if (metadata()?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive MD5 checksum</td>
|
||||
<td i18n>Archive SHA256 checksum</td>
|
||||
<td>{{metadata()?.archive_checksum}}</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ describe('DocumentDetailComponent', () => {
|
||||
const deleteButtons = fixture.debugElement.queryAll(
|
||||
By.css('pngx-confirm-button')
|
||||
)
|
||||
expect(deleteButtons.length).toEqual(1)
|
||||
expect(deleteButtons).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should fall back to details tab when duplicates tab is active but no duplicates', () => {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if (loading()) {
|
||||
<div class="d-flex">
|
||||
<output class="spinner-border spinner-border-sm fw-normal" role="status"></output>
|
||||
<output class="spinner-border spinner-border-sm fw-normal"></output>
|
||||
</div>
|
||||
} @else {
|
||||
<ul class="list-group">
|
||||
|
||||
+6
-6
@@ -26,11 +26,11 @@ import { UserService } from 'src/app/services/rest/user.service'
|
||||
],
|
||||
})
|
||||
export class DocumentHistoryComponent implements OnInit {
|
||||
private documentService = inject(DocumentService)
|
||||
private correspondentService = inject(CorrespondentService)
|
||||
private storagePathService = inject(StoragePathService)
|
||||
private documentTypeService = inject(DocumentTypeService)
|
||||
private userService = inject(UserService)
|
||||
private readonly documentService = inject(DocumentService)
|
||||
private readonly correspondentService = inject(CorrespondentService)
|
||||
private readonly storagePathService = inject(StoragePathService)
|
||||
private readonly documentTypeService = inject(DocumentTypeService)
|
||||
private readonly userService = inject(UserService)
|
||||
|
||||
public AuditLogAction = AuditLogAction
|
||||
|
||||
@@ -70,7 +70,7 @@ export class DocumentHistoryComponent implements OnInit {
|
||||
return cached
|
||||
}
|
||||
|
||||
const idInt = parseInt(id, 10)
|
||||
const idInt = Number.parseInt(id, 10)
|
||||
const fallback$ = of(id)
|
||||
|
||||
let result$: Observable<string>
|
||||
|
||||
+6
-1
@@ -7,8 +7,9 @@
|
||||
<div class="px-3 py-2 mb-2">
|
||||
@if (versionUploadState === UploadState.Idle) {
|
||||
<div class="input-group input-group-sm mb-2">
|
||||
<span class="input-group-text" i18n>Label</span>
|
||||
<label class="input-group-text" for="newVersionLabel" i18n>Label</label>
|
||||
<input
|
||||
id="newVersionLabel"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="newVersionLabel"
|
||||
@@ -70,9 +71,13 @@
|
||||
}
|
||||
</div>
|
||||
<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">
|
||||
@if (isEditingVersion(version.id)) {
|
||||
<input
|
||||
id="versionLabel{{ version.id }}"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="versionLabelDraft"
|
||||
|
||||
+1
-1
@@ -328,7 +328,7 @@ describe('FilterEditorComponent', () => {
|
||||
})
|
||||
|
||||
it('should ingest legacy text filter rules for doc title + content', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
expect(component.textFilter).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_TITLE_CONTENT,
|
||||
|
||||
+4
-4
@@ -1,18 +1,18 @@
|
||||
<div class="row mb-3">
|
||||
<div class="col mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
|
||||
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
|
||||
<label class="text-muted me-2 mb-0" for="managementNameFilter" 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<div class="input-group input-group-sm w-auto d-none d-md-flex">
|
||||
<span class="input-group-text border-0" i18n>Show:</span>
|
||||
<label class="input-group-text border-0" for="managementPageSize" i18n>Show:</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm w-auto me-3">
|
||||
<select class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
||||
<select id="managementPageSize" class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
||||
<option [ngValue]="25">25</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
<option [ngValue]="100">100</option>
|
||||
|
||||
+2
-2
@@ -83,12 +83,12 @@ describe('TagListComponent', () => {
|
||||
]
|
||||
component['_nameFilter'] = null // Simulate empty name filter
|
||||
const filtered = component.filterData(tags as any)
|
||||
expect(filtered.length).toBe(2)
|
||||
expect(filtered).toHaveLength(2)
|
||||
expect(filtered.find((t) => t.id === 2)).toBeUndefined()
|
||||
|
||||
component['_nameFilter'] = 'Tag2' // Simulate non-empty name filter
|
||||
const filteredWithName = component.filterData(tags as any)
|
||||
expect(filteredWithName.length).toBe(2)
|
||||
expect(filteredWithName).toHaveLength(2)
|
||||
expect(filteredWithName.find((t) => t.id === 2)).toBeUndefined()
|
||||
expect(
|
||||
filteredWithName
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<th scope="col" style="width: 40px;">
|
||||
<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();">
|
||||
<label class="form-check-label" for="all-objects"></label>
|
||||
<label class="form-check-label" for="all-objects"><span class="visually-hidden" i18n>Select all processed email messages</span></label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" i18n>Subject</th>
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { normalizeSync } from 'normalize-diacritics'
|
||||
|
||||
export type SearchTextValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| bigint
|
||||
| null
|
||||
| undefined
|
||||
string | number | boolean | bigint | null | undefined
|
||||
|
||||
export function normalizeSearchText(value: SearchTextValue): string {
|
||||
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
+1238
-1156
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
+1294
-1211
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
+1419
-1337
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
@@ -341,6 +341,35 @@ class PaperlessCommand(RichCommand):
|
||||
|
||||
return None
|
||||
|
||||
def _resolve_iterable(
|
||||
self,
|
||||
iterable: Iterable[T],
|
||||
*,
|
||||
chunk_size: int = 2000,
|
||||
) -> Iterable[T]:
|
||||
"""
|
||||
Convert a raw Django QuerySet into a server-side iterator.
|
||||
|
||||
Plain iteration over a QuerySet (`for x in queryset`) loads every
|
||||
row into the queryset's result cache before yielding the first
|
||||
item, defeating the purpose of streaming via track()/
|
||||
track_with_stats(). Wrapping it in .iterator() streams rows from
|
||||
the database instead. Non-QuerySet iterables (generators, lists,
|
||||
already-wrapped iterators) are returned unchanged.
|
||||
|
||||
Args:
|
||||
iterable: The items to iterate over.
|
||||
chunk_size: Row batch size passed to QuerySet.iterator().
|
||||
|
||||
Returns:
|
||||
A QuerySet.iterator() if given a QuerySet, else the original
|
||||
iterable.
|
||||
"""
|
||||
if isinstance(iterable, QuerySet):
|
||||
return iterable.iterator(chunk_size=chunk_size)
|
||||
|
||||
return iterable
|
||||
|
||||
def track(
|
||||
self,
|
||||
iterable: Iterable[T],
|
||||
@@ -367,13 +396,14 @@ class PaperlessCommand(RichCommand):
|
||||
for doc in self.track(documents, description="Renaming..."):
|
||||
process(doc)
|
||||
"""
|
||||
if total is None:
|
||||
total = self._get_iterable_length(iterable)
|
||||
iterable = self._resolve_iterable(iterable)
|
||||
|
||||
if self.no_progress_bar:
|
||||
yield from iterable
|
||||
return
|
||||
|
||||
if total is None:
|
||||
total = self._get_iterable_length(iterable)
|
||||
|
||||
with self._create_progress(description) as progress:
|
||||
task_id = progress.add_task(description, total=total)
|
||||
for item in iterable:
|
||||
@@ -438,13 +468,14 @@ class PaperlessCommand(RichCommand):
|
||||
except Exception:
|
||||
stats.failed += 1
|
||||
"""
|
||||
if total is None:
|
||||
total = self._get_iterable_length(iterable)
|
||||
iterable = self._resolve_iterable(iterable)
|
||||
|
||||
if self.no_progress_bar:
|
||||
yield from iterable
|
||||
return
|
||||
|
||||
if total is None:
|
||||
total = self._get_iterable_length(iterable)
|
||||
|
||||
stderr_console = Console(stderr=True)
|
||||
|
||||
# Progress is created without its own console so Live controls rendering.
|
||||
|
||||
@@ -54,8 +54,12 @@ class Command(PaperlessCommand):
|
||||
else:
|
||||
documents = Document.objects.all()
|
||||
|
||||
documents = documents.only("id", "archive_filename")
|
||||
|
||||
document_ids = [
|
||||
doc.id for doc in documents if overwrite or not doc.has_archive_version
|
||||
doc.id
|
||||
for doc in documents.iterator(chunk_size=2000)
|
||||
if overwrite or not doc.has_archive_version
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
@@ -48,6 +48,9 @@ _LANGUAGE_MAP: dict[str, str] = {
|
||||
}
|
||||
|
||||
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:
|
||||
@@ -77,10 +80,10 @@ def register_tokenizers(index: tantivy.Index, language: str | None) -> None:
|
||||
|
||||
|
||||
def _paperless_text(language: str | None) -> tantivy.TextAnalyzer:
|
||||
"""Main full-text tokenizer for content, title, etc: simple -> remove_long(65) -> lowercase -> ascii_fold [-> stemmer]"""
|
||||
"""Main full-text tokenizer for content, title, etc: simple -> remove_long(129) -> lowercase -> ascii_fold [-> stemmer]"""
|
||||
builder = (
|
||||
tantivy.TextAnalyzerBuilder(tantivy.Tokenizer.simple())
|
||||
.filter(tantivy.Filter.remove_long(65))
|
||||
.filter(tantivy.Filter.remove_long(_TOKEN_REMOVE_LONG_LIMIT))
|
||||
.filter(tantivy.Filter.lowercase())
|
||||
.filter(tantivy.Filter.ascii_fold())
|
||||
)
|
||||
@@ -119,12 +122,12 @@ def _bigram_analyzer() -> tantivy.TextAnalyzer:
|
||||
|
||||
|
||||
def _simple_search_analyzer() -> tantivy.TextAnalyzer:
|
||||
"""Tokenizer for simple substring search fields: non-whitespace chunks -> remove_long(65) -> lowercase -> ascii_fold."""
|
||||
"""Tokenizer for simple substring search fields: non-whitespace chunks -> remove_long(129) -> lowercase -> ascii_fold."""
|
||||
return (
|
||||
tantivy.TextAnalyzerBuilder(
|
||||
tantivy.Tokenizer.regex(r"\S+"),
|
||||
)
|
||||
.filter(tantivy.Filter.remove_long(65))
|
||||
.filter(tantivy.Filter.remove_long(_TOKEN_REMOVE_LONG_LIMIT))
|
||||
.filter(tantivy.Filter.lowercase())
|
||||
.filter(tantivy.Filter.ascii_fold())
|
||||
.build()
|
||||
|
||||
@@ -163,7 +163,12 @@ def scan(query: str) -> list[Token]:
|
||||
i += 1
|
||||
continue
|
||||
token, i = matched
|
||||
_flush(buf, tokens)
|
||||
if buf and buf[-1] == ",":
|
||||
buf.pop()
|
||||
_flush(buf, tokens)
|
||||
tokens.append(Comma())
|
||||
else:
|
||||
_flush(buf, tokens)
|
||||
tokens.append(token)
|
||||
i = _maybe_comma(query, i, tokens)
|
||||
_flush(buf, tokens)
|
||||
|
||||
@@ -127,12 +127,19 @@ def mock_queryset():
|
||||
def __init__(self, items: list):
|
||||
self._items = items
|
||||
self.count_called = False
|
||||
self.iterator_chunk_size: int | None = None
|
||||
|
||||
def count(self) -> int:
|
||||
self.count_called = True
|
||||
return len(self._items)
|
||||
|
||||
def __iter__(self):
|
||||
raise AssertionError(
|
||||
"querysets should be streamed via .iterator(), not iterated directly",
|
||||
)
|
||||
|
||||
def iterator(self, chunk_size: int | None = None):
|
||||
self.iterator_chunk_size = chunk_size
|
||||
return iter(self._items)
|
||||
|
||||
def __len__(self):
|
||||
@@ -405,6 +412,21 @@ class TestTrack:
|
||||
spy.assert_called_once_with(queryset)
|
||||
assert queryset.count_called is True
|
||||
|
||||
def test_streams_querysets_via_iterator(
|
||||
self,
|
||||
simple_command: SimpleCommand,
|
||||
mock_queryset,
|
||||
) -> None:
|
||||
"""Verify track() streams querysets via .iterator() rather than
|
||||
materializing the full result cache with a plain for-loop."""
|
||||
simple_command.no_progress_bar = False
|
||||
queryset = mock_queryset([1, 2, 3])
|
||||
|
||||
result = list(simple_command.track(queryset))
|
||||
|
||||
assert result == [1, 2, 3]
|
||||
assert queryset.iterator_chunk_size == 2000
|
||||
|
||||
|
||||
@pytest.mark.management
|
||||
class TestProcessParallel:
|
||||
|
||||
@@ -261,6 +261,36 @@ class TestSearch:
|
||||
== 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(
|
||||
("mode", "title", "content", "hits", "misses"),
|
||||
[
|
||||
|
||||
@@ -99,6 +99,25 @@ class TestTokenizers:
|
||||
)
|
||||
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:
|
||||
"""Unsupported language codes should log a warning and disable stemming gracefully."""
|
||||
sb = tantivy.SchemaBuilder()
|
||||
|
||||
@@ -716,6 +716,12 @@ class TestISODatetimeBounds:
|
||||
"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:
|
||||
# A token with "T" that is not valid ISO datetime -> raise.
|
||||
with pytest.raises(InvalidDateQuery) as exc_info:
|
||||
|
||||
@@ -3769,13 +3769,20 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
|
||||
validated_data=serializer.validated_data,
|
||||
)
|
||||
documents = Document.objects.filter(pk__in=ids)
|
||||
versioned_documents = []
|
||||
compression = serializer.validated_data.get("compression")
|
||||
content = serializer.validated_data.get("content")
|
||||
follow_filename_format = serializer.validated_data.get("follow_formatting")
|
||||
|
||||
for document in documents:
|
||||
if not has_perms_owner_aware(request.user, "change_document", document):
|
||||
root_doc = get_root_document(document)
|
||||
if not has_perms_owner_aware(request.user, "view_document", root_doc):
|
||||
return HttpResponseForbidden("Insufficient permissions")
|
||||
versioned_documents.append(
|
||||
get_latest_version_for_root(
|
||||
root_doc,
|
||||
),
|
||||
)
|
||||
|
||||
if content == "both":
|
||||
strategy_class = OriginalAndArchiveStrategy
|
||||
@@ -3798,7 +3805,7 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
|
||||
zipf,
|
||||
follow_formatting=follow_filename_format,
|
||||
)
|
||||
for document in documents:
|
||||
for document in versioned_documents:
|
||||
strategy.add_document(document)
|
||||
|
||||
f = temp_path.open("rb")
|
||||
|
||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user