mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			4e0f5dff95
			...
			feature-in
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f20b224ef5 | ||
|   | 8344a6e0e3 | ||
|   | 10a8a5da19 | ||
|   | d190b50032 | ||
|   | fda29f51c3 | 
| @@ -1,10 +1,11 @@ | |||||||
| { | { | ||||||
|     "python.testing.pytestArgs": [], |     "python.testing.pytestArgs": [ | ||||||
|  |         "src" | ||||||
|  |     ], | ||||||
|     "python.testing.unittestEnabled": false, |     "python.testing.unittestEnabled": false, | ||||||
|     "python.testing.pytestEnabled": true, |     "python.testing.pytestEnabled": true, | ||||||
|     "files.watcherExclude": { |     "files.watcherExclude": { | ||||||
|         "**/.venv/**": true, |         "**/.venv/**": true, | ||||||
|         "**/pytest_cache/**": true |         "**/pytest_cache/**": true | ||||||
|     }, |     } | ||||||
|     "python.testing.cwd": "${workspaceFolder}/src" |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -32,7 +32,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.8.13-python3.12-bookworm-slim AS s6-overlay-base | FROM ghcr.io/astral-sh/uv:0.8.8-python3.12-bookworm-slim AS s6-overlay-base | ||||||
|  |  | ||||||
| WORKDIR /usr/src/s6 | WORKDIR /usr/src/s6 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| # correct networking for the tests | # correct networking for the tests | ||||||
| services: | services: | ||||||
|   gotenberg: |   gotenberg: | ||||||
|     image: docker.io/gotenberg/gotenberg:8.22 |     image: docker.io/gotenberg/gotenberg:8.20 | ||||||
|     hostname: gotenberg |     hostname: gotenberg | ||||||
|     container_name: gotenberg |     container_name: gotenberg | ||||||
|     network_mode: host |     network_mode: host | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - redisdata:/data |       - redisdata:/data | ||||||
|   db: |   db: | ||||||
|     image: docker.io/library/mariadb:12 |     image: docker.io/library/mariadb:11 | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     volumes: |     volumes: | ||||||
|       - dbdata:/var/lib/mysql |       - dbdata:/var/lib/mysql | ||||||
| @@ -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.22 |     image: docker.io/gotenberg/gotenberg:8.20 | ||||||
|     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. | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - redisdata:/data |       - redisdata:/data | ||||||
|   db: |   db: | ||||||
|     image: docker.io/library/mariadb:12 |     image: docker.io/library/mariadb:11 | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     volumes: |     volumes: | ||||||
|       - dbdata:/var/lib/mysql |       - dbdata:/var/lib/mysql | ||||||
|   | |||||||
| @@ -66,7 +66,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.22 |     image: docker.io/gotenberg/gotenberg:8.20 | ||||||
|     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. | ||||||
|   | |||||||
| @@ -55,7 +55,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.22 |     image: docker.io/gotenberg/gotenberg:8.20 | ||||||
|     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. | ||||||
|   | |||||||
| @@ -2544,11 +2544,11 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1017</context> |           <context context-type="linenumber">1011</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1382</context> |           <context context-type="linenumber">1373</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.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||||
| @@ -3156,7 +3156,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">970</context> |           <context context-type="linenumber">964</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.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||||
| @@ -6579,7 +6579,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1381</context> |           <context context-type="linenumber">1372</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6490688569532630280" datatype="html"> |       <trans-unit id="6490688569532630280" datatype="html"> | ||||||
| @@ -6869,56 +6869,56 @@ | |||||||
|         <source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source> |         <source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">410,412</context> |           <context context-type="linenumber">412,414</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3200733026060976258" datatype="html"> |       <trans-unit id="3200733026060976258" datatype="html"> | ||||||
|         <source>Document changes detected</source> |         <source>Document changes detected</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">444</context> |           <context context-type="linenumber">435</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2887155916749964" datatype="html"> |       <trans-unit id="2887155916749964" datatype="html"> | ||||||
|         <source>The version of this document in your browser session appears older than the existing version.</source> |         <source>The version of this document in your browser session appears older than the existing version.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">445</context> |           <context context-type="linenumber">436</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="237142428785956348" datatype="html"> |       <trans-unit id="237142428785956348" datatype="html"> | ||||||
|         <source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source> |         <source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">446</context> |           <context context-type="linenumber">437</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8720977247725652816" datatype="html"> |       <trans-unit id="8720977247725652816" datatype="html"> | ||||||
|         <source>Ok</source> |         <source>Ok</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">448</context> |           <context context-type="linenumber">439</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6142395741265832184" datatype="html"> |       <trans-unit id="6142395741265832184" datatype="html"> | ||||||
|         <source>Next document</source> |         <source>Next document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">573</context> |           <context context-type="linenumber">563</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="651985345816518480" datatype="html"> |       <trans-unit id="651985345816518480" datatype="html"> | ||||||
|         <source>Previous document</source> |         <source>Previous document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">583</context> |           <context context-type="linenumber">573</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2885986061416655600" datatype="html"> |       <trans-unit id="2885986061416655600" datatype="html"> | ||||||
|         <source>Close document</source> |         <source>Close document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">591</context> |           <context context-type="linenumber">581</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> |           <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> | ||||||
| @@ -6929,67 +6929,67 @@ | |||||||
|         <source>Save document</source> |         <source>Save document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">598</context> |           <context context-type="linenumber">588</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1784543155727940353" datatype="html"> |       <trans-unit id="1784543155727940353" datatype="html"> | ||||||
|         <source>Save and close / next</source> |         <source>Save and close / next</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">607</context> |           <context context-type="linenumber">597</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5758784066858623886" datatype="html"> |       <trans-unit id="5758784066858623886" datatype="html"> | ||||||
|         <source>Error retrieving metadata</source> |         <source>Error retrieving metadata</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">659</context> |           <context context-type="linenumber">649</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3456881259945295697" datatype="html"> |       <trans-unit id="3456881259945295697" datatype="html"> | ||||||
|         <source>Error retrieving suggestions.</source> |         <source>Error retrieving suggestions.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">688</context> |           <context context-type="linenumber">678</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2194092841814123758" datatype="html"> |       <trans-unit id="2194092841814123758" datatype="html"> | ||||||
|         <source>Document "<x id="PH" equiv-text="newValues.title"/>" saved successfully.</source> |         <source>Document "<x id="PH" equiv-text="newValues.title"/>" saved successfully.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">860</context> |           <context context-type="linenumber">858</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">884</context> |           <context context-type="linenumber">882</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6626387786259219838" datatype="html"> |       <trans-unit id="6626387786259219838" datatype="html"> | ||||||
|         <source>Error saving document "<x id="PH" equiv-text="this.document.title"/>"</source> |         <source>Error saving document "<x id="PH" equiv-text="this.document.title"/>"</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">890</context> |           <context context-type="linenumber">888</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="448882439049417053" datatype="html"> |       <trans-unit id="448882439049417053" datatype="html"> | ||||||
|         <source>Error saving document</source> |         <source>Error saving document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">939</context> |           <context context-type="linenumber">933</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8410796510716511826" datatype="html"> |       <trans-unit id="8410796510716511826" datatype="html"> | ||||||
|         <source>Do you really want to move the document "<x id="PH" equiv-text="this.document.title"/>" to the trash?</source> |         <source>Do you really want to move the document "<x id="PH" equiv-text="this.document.title"/>" to the trash?</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">971</context> |           <context context-type="linenumber">965</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="282586936710748252" datatype="html"> |       <trans-unit id="282586936710748252" datatype="html"> | ||||||
|         <source>Documents can be restored prior to permanent deletion.</source> |         <source>Documents can be restored prior to permanent deletion.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">972</context> |           <context context-type="linenumber">966</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.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||||
| @@ -7000,7 +7000,7 @@ | |||||||
|         <source>Move to trash</source> |         <source>Move to trash</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">974</context> |           <context context-type="linenumber">968</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.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||||
| @@ -7011,14 +7011,14 @@ | |||||||
|         <source>Error deleting document</source> |         <source>Error deleting document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">993</context> |           <context context-type="linenumber">987</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="619486176823357521" datatype="html"> |       <trans-unit id="619486176823357521" datatype="html"> | ||||||
|         <source>Reprocess confirm</source> |         <source>Reprocess confirm</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1013</context> |           <context context-type="linenumber">1007</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.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||||
| @@ -7029,67 +7029,67 @@ | |||||||
|         <source>This operation will permanently recreate the archive file for this document.</source> |         <source>This operation will permanently recreate the archive file for this document.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1014</context> |           <context context-type="linenumber">1008</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="302054111564709516" datatype="html"> |       <trans-unit id="302054111564709516" datatype="html"> | ||||||
|         <source>The archive file will be re-generated with the current settings.</source> |         <source>The archive file will be re-generated with the current settings.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1015</context> |           <context context-type="linenumber">1009</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8251197608401006898" datatype="html"> |       <trans-unit id="8251197608401006898" datatype="html"> | ||||||
|         <source>Reprocess operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source> |         <source>Reprocess operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1025</context> |           <context context-type="linenumber">1019</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4409560272830824468" datatype="html"> |       <trans-unit id="4409560272830824468" datatype="html"> | ||||||
|         <source>Error executing operation</source> |         <source>Error executing operation</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1036</context> |           <context context-type="linenumber">1030</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6030453331794586802" datatype="html"> |       <trans-unit id="6030453331794586802" datatype="html"> | ||||||
|         <source>Error downloading document</source> |         <source>Error downloading document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1085</context> |           <context context-type="linenumber">1079</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4458954481601077369" datatype="html"> |       <trans-unit id="4458954481601077369" datatype="html"> | ||||||
|         <source>Page Fit</source> |         <source>Page Fit</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1162</context> |           <context context-type="linenumber">1156</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4663705961777238777" datatype="html"> |       <trans-unit id="4663705961777238777" datatype="html"> | ||||||
|         <source>PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source> |         <source>PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1400</context> |           <context context-type="linenumber">1391</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="9043972994040261999" datatype="html"> |       <trans-unit id="9043972994040261999" datatype="html"> | ||||||
|         <source>Error executing PDF edit operation</source> |         <source>Error executing PDF edit operation</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1412</context> |           <context context-type="linenumber">1403</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6085793215710522488" datatype="html"> |       <trans-unit id="6085793215710522488" datatype="html"> | ||||||
|         <source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source> |         <source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1479</context> |           <context context-type="linenumber">1463</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||||
|           <context context-type="linenumber">1483</context> |           <context context-type="linenumber">1467</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4958946940233632319" datatype="html"> |       <trans-unit id="4958946940233632319" datatype="html"> | ||||||
| @@ -7671,7 +7671,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">313</context> |           <context context-type="linenumber">311</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1494518490116523821" datatype="html"> |       <trans-unit id="1494518490116523821" datatype="html"> | ||||||
| @@ -7682,7 +7682,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">306</context> |           <context context-type="linenumber">304</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8461842260159597706" datatype="html"> |       <trans-unit id="8461842260159597706" datatype="html"> | ||||||
| @@ -7925,49 +7925,49 @@ | |||||||
|         <source>Reset filters / selection</source> |         <source>Reset filters / selection</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">294</context> |           <context context-type="linenumber">292</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4135055128446167640" datatype="html"> |       <trans-unit id="4135055128446167640" datatype="html"> | ||||||
|         <source>Open first [selected] document</source> |         <source>Open first [selected] document</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">322</context> |           <context context-type="linenumber">320</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3629960544875360046" datatype="html"> |       <trans-unit id="3629960544875360046" datatype="html"> | ||||||
|         <source>Previous page</source> |         <source>Previous page</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">338</context> |           <context context-type="linenumber">336</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3337301694210287595" datatype="html"> |       <trans-unit id="3337301694210287595" datatype="html"> | ||||||
|         <source>Next page</source> |         <source>Next page</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">350</context> |           <context context-type="linenumber">348</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2155249406916744630" datatype="html"> |       <trans-unit id="2155249406916744630" datatype="html"> | ||||||
|         <source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source> |         <source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">383</context> |           <context context-type="linenumber">381</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4646273665293421938" datatype="html"> |       <trans-unit id="4646273665293421938" datatype="html"> | ||||||
|         <source>Failed to save view "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>".</source> |         <source>Failed to save view "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>".</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">389</context> |           <context context-type="linenumber">387</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6837554170707123455" datatype="html"> |       <trans-unit id="6837554170707123455" datatype="html"> | ||||||
|         <source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source> |         <source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
|           <context context-type="linenumber">435</context> |           <context context-type="linenumber">431</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="739880801667335279" datatype="html"> |       <trans-unit id="739880801667335279" datatype="html"> | ||||||
|   | |||||||
| @@ -106,7 +106,6 @@ describe('DashboardComponent', () => { | |||||||
|               }), |               }), | ||||||
|             dashboardViews: saved_views.filter((v) => v.show_on_dashboard), |             dashboardViews: saved_views.filter((v) => v.show_on_dashboard), | ||||||
|             allViews: saved_views, |             allViews: saved_views, | ||||||
|             setDocumentCount: jest.fn(), |  | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|         provideHttpClient(withInterceptorsFromDi()), |         provideHttpClient(withInterceptorsFromDi()), | ||||||
|   | |||||||
| @@ -52,7 +52,6 @@ import { | |||||||
| } from 'src/app/services/permissions.service' | } from 'src/app/services/permissions.service' | ||||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||||
| import { DocumentService } from 'src/app/services/rest/document.service' | import { DocumentService } from 'src/app/services/rest/document.service' | ||||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' |  | ||||||
| import { SettingsService } from 'src/app/services/settings.service' | import { SettingsService } from 'src/app/services/settings.service' | ||||||
| import { WebsocketStatusService } from 'src/app/services/websocket-status.service' | import { WebsocketStatusService } from 'src/app/services/websocket-status.service' | ||||||
| import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' | import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' | ||||||
| @@ -95,7 +94,6 @@ export class SavedViewWidgetComponent | |||||||
|   permissionsService = inject(PermissionsService) |   permissionsService = inject(PermissionsService) | ||||||
|   private settingsService = inject(SettingsService) |   private settingsService = inject(SettingsService) | ||||||
|   private customFieldService = inject(CustomFieldsService) |   private customFieldService = inject(CustomFieldsService) | ||||||
|   private savedViewService = inject(SavedViewService) |  | ||||||
|  |  | ||||||
|   public DisplayMode = DisplayMode |   public DisplayMode = DisplayMode | ||||||
|   public DisplayField = DisplayField |   public DisplayField = DisplayField | ||||||
| @@ -183,7 +181,6 @@ export class SavedViewWidgetComponent | |||||||
|           this.show = true |           this.show = true | ||||||
|           this.documents = result.results |           this.documents = result.results | ||||||
|           this.count = result.count |           this.count = result.count | ||||||
|           this.savedViewService.setDocumentCount(this.savedView, result.count) |  | ||||||
|         }), |         }), | ||||||
|         delay(500) |         delay(500) | ||||||
|       ) |       ) | ||||||
|   | |||||||
| @@ -1,4 +1,9 @@ | |||||||
| <pngx-page-header [(title)]="title"> | <pngx-page-header [(title)]="title"> | ||||||
|  |   @if (document?.in_process) { | ||||||
|  |     <span class="badge bg-danger text-dark ms-2 d-flex align-items-center"> | ||||||
|  |       <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |     </span> | ||||||
|  |   } | ||||||
|   @if (archiveContentRenderType === ContentRenderType.PDF && !useNativePdfViewer) { |   @if (archiveContentRenderType === ContentRenderType.PDF && !useNativePdfViewer) { | ||||||
|     @if (previewNumPages) { |     @if (previewNumPages) { | ||||||
|       <div class="input-group input-group-sm d-none d-md-flex"> |       <div class="input-group input-group-sm d-none d-md-flex"> | ||||||
| @@ -50,7 +55,7 @@ | |||||||
|       <div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div> |       <div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div> | ||||||
|     </button> |     </button> | ||||||
|     <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow"> |     <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow"> | ||||||
|       <button ngbDropdownItem (click)="reprocess()" [disabled]="!userCanEdit || !userIsOwner"> |       <button ngbDropdownItem (click)="reprocess()" [disabled]="!userCanEdit || !userIsOwner || document?.in_process"> | ||||||
|         <i-bs width="1em" height="1em" name="arrow-counterclockwise"></i-bs> <span i18n>Reprocess</span> |         <i-bs width="1em" height="1em" name="arrow-counterclockwise"></i-bs> <span i18n>Reprocess</span> | ||||||
|       </button> |       </button> | ||||||
|  |  | ||||||
| @@ -58,7 +63,7 @@ | |||||||
|         <i-bs width="1em" height="1em" name="diagram-3"></i-bs> <span i18n>More like this</span> |         <i-bs width="1em" height="1em" name="diagram-3"></i-bs> <span i18n>More like this</span> | ||||||
|       </button> |       </button> | ||||||
|  |  | ||||||
|       <button ngbDropdownItem (click)="editPdf()" [disabled]="!userIsOwner || !userCanEdit || originalContentRenderType !== ContentRenderType.PDF"> |       <button ngbDropdownItem (click)="editPdf()" [disabled]="!userIsOwner || !userCanEdit || originalContentRenderType !== ContentRenderType.PDF || document?.in_process"> | ||||||
|         <i-bs name="pencil"></i-bs> <ng-container i18n>PDF Editor</ng-container> |         <i-bs name="pencil"></i-bs> <ng-container i18n>PDF Editor</ng-container> | ||||||
|       </button> |       </button> | ||||||
|     </div> |     </div> | ||||||
| @@ -90,7 +95,6 @@ | |||||||
|       } |       } | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
| </pngx-page-header> | </pngx-page-header> | ||||||
|  |  | ||||||
| <div class="row"> | <div class="row"> | ||||||
|   | |||||||
| @@ -21,9 +21,8 @@ import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | |||||||
| import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer' | import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer' | ||||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||||
| import { DeviceDetectorService } from 'ngx-device-detector' | import { DeviceDetectorService } from 'ngx-device-detector' | ||||||
| import { BehaviorSubject, Observable, of, Subject } from 'rxjs' | import { BehaviorSubject, Observable, Subject } from 'rxjs' | ||||||
| import { | import { | ||||||
|   catchError, |  | ||||||
|   debounceTime, |   debounceTime, | ||||||
|   distinctUntilChanged, |   distinctUntilChanged, | ||||||
|   filter, |   filter, | ||||||
| @@ -328,163 +327,19 @@ export class DocumentDetailComponent | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private mapDocToForm(doc: Document): any { |  | ||||||
|     return { |  | ||||||
|       ...doc, |  | ||||||
|       permissions_form: { owner: doc.owner, set_permissions: doc.permissions }, |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private mapFormToDoc(value: any): any { |  | ||||||
|     const docValues = { ...value } |  | ||||||
|     docValues['owner'] = value['permissions_form']?.owner |  | ||||||
|     docValues['set_permissions'] = value['permissions_form']?.set_permissions |  | ||||||
|     delete docValues['permissions_form'] |  | ||||||
|     return docValues |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private prepareForm(doc: Document): void { |  | ||||||
|     this.documentForm.reset(this.mapDocToForm(doc), { emitEvent: false }) |  | ||||||
|     if (!this.userCanEditDoc(doc)) { |  | ||||||
|       this.documentForm.disable({ emitEvent: false }) |  | ||||||
|     } else { |  | ||||||
|       this.documentForm.enable({ emitEvent: false }) |  | ||||||
|     } |  | ||||||
|     if (doc.__changedFields) { |  | ||||||
|       doc.__changedFields.forEach((field) => { |  | ||||||
|         if (field === 'owner' || field === 'set_permissions') { |  | ||||||
|           this.documentForm.get('permissions_form')?.markAsDirty() |  | ||||||
|         } else { |  | ||||||
|           this.documentForm.get(field)?.markAsDirty() |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private setupDirtyTracking( |  | ||||||
|     currentDocument: Document, |  | ||||||
|     originalDocument: Document |  | ||||||
|   ): void { |  | ||||||
|     this.store = new BehaviorSubject({ |  | ||||||
|       title: originalDocument.title, |  | ||||||
|       content: originalDocument.content, |  | ||||||
|       created: originalDocument.created, |  | ||||||
|       correspondent: originalDocument.correspondent, |  | ||||||
|       document_type: originalDocument.document_type, |  | ||||||
|       storage_path: originalDocument.storage_path, |  | ||||||
|       archive_serial_number: originalDocument.archive_serial_number, |  | ||||||
|       tags: [...originalDocument.tags], |  | ||||||
|       permissions_form: { |  | ||||||
|         owner: originalDocument.owner, |  | ||||||
|         set_permissions: originalDocument.permissions, |  | ||||||
|       }, |  | ||||||
|       custom_fields: [...originalDocument.custom_fields], |  | ||||||
|     }) |  | ||||||
|     this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable()) |  | ||||||
|     this.isDirty$ |  | ||||||
|       .pipe( |  | ||||||
|         takeUntil(this.unsubscribeNotifier), |  | ||||||
|         takeUntil(this.docChangeNotifier) |  | ||||||
|       ) |  | ||||||
|       .subscribe((dirty) => |  | ||||||
|         this.openDocumentService.setDirty( |  | ||||||
|           currentDocument, |  | ||||||
|           dirty, |  | ||||||
|           this.getChangedFields() |  | ||||||
|         ) |  | ||||||
|       ) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private loadDocument(documentId: number): void { |  | ||||||
|     this.previewUrl = this.documentsService.getPreviewUrl(documentId) |  | ||||||
|     this.http |  | ||||||
|       .get(this.previewUrl, { responseType: 'text' }) |  | ||||||
|       .pipe( |  | ||||||
|         first(), |  | ||||||
|         takeUntil(this.unsubscribeNotifier), |  | ||||||
|         takeUntil(this.docChangeNotifier) |  | ||||||
|       ) |  | ||||||
|       .subscribe({ |  | ||||||
|         next: (res) => (this.previewText = res.toString()), |  | ||||||
|         error: (err) => |  | ||||||
|           (this.previewText = $localize`An error occurred loading content: ${ |  | ||||||
|             err.message ?? err.toString() |  | ||||||
|           }`), |  | ||||||
|       }) |  | ||||||
|     this.thumbUrl = this.documentsService.getThumbUrl(documentId) |  | ||||||
|     this.documentsService |  | ||||||
|       .get(documentId) |  | ||||||
|       .pipe( |  | ||||||
|         catchError(() => { |  | ||||||
|           // 404 is handled in the subscribe below |  | ||||||
|           return of(null) |  | ||||||
|         }), |  | ||||||
|         first(), |  | ||||||
|         takeUntil(this.unsubscribeNotifier), |  | ||||||
|         takeUntil(this.docChangeNotifier) |  | ||||||
|       ) |  | ||||||
|       .subscribe({ |  | ||||||
|         next: (doc) => { |  | ||||||
|           if (!doc) { |  | ||||||
|             this.router.navigate(['404'], { replaceUrl: true }) |  | ||||||
|             return |  | ||||||
|           } |  | ||||||
|           this.documentId = doc.id |  | ||||||
|           this.suggestions = null |  | ||||||
|           const openDocument = this.openDocumentService.getOpenDocument( |  | ||||||
|             this.documentId |  | ||||||
|           ) |  | ||||||
|           const useDoc = openDocument || doc |  | ||||||
|           if (openDocument) { |  | ||||||
|             if ( |  | ||||||
|               new Date(doc.modified) > new Date(openDocument.modified) && |  | ||||||
|               !this.modalService.hasOpenModals() |  | ||||||
|             ) { |  | ||||||
|               const modal = this.modalService.open(ConfirmDialogComponent) |  | ||||||
|               modal.componentInstance.title = $localize`Document changes detected` |  | ||||||
|               modal.componentInstance.messageBold = $localize`The version of this document in your browser session appears older than the existing version.` |  | ||||||
|               modal.componentInstance.message = $localize`Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.` |  | ||||||
|               modal.componentInstance.cancelBtnClass = 'visually-hidden' |  | ||||||
|               modal.componentInstance.btnCaption = $localize`Ok` |  | ||||||
|               modal.componentInstance.confirmClicked.subscribe(() => |  | ||||||
|                 modal.close() |  | ||||||
|               ) |  | ||||||
|             } |  | ||||||
|           } else { |  | ||||||
|             this.openDocumentService |  | ||||||
|               .openDocument(doc) |  | ||||||
|               .pipe( |  | ||||||
|                 first(), |  | ||||||
|                 takeUntil(this.unsubscribeNotifier), |  | ||||||
|                 takeUntil(this.docChangeNotifier) |  | ||||||
|               ) |  | ||||||
|               .subscribe() |  | ||||||
|           } |  | ||||||
|           this.updateComponent(useDoc) |  | ||||||
|           this.titleSubject |  | ||||||
|             .pipe( |  | ||||||
|               debounceTime(1000), |  | ||||||
|               distinctUntilChanged(), |  | ||||||
|               takeUntil(this.docChangeNotifier), |  | ||||||
|               takeUntil(this.unsubscribeNotifier) |  | ||||||
|             ) |  | ||||||
|             .subscribe((titleValue) => { |  | ||||||
|               if (titleValue !== this.titleInput.value) return |  | ||||||
|               this.title = titleValue |  | ||||||
|               this.documentForm.patchValue({ title: titleValue }) |  | ||||||
|             }) |  | ||||||
|           this.setupDirtyTracking(useDoc, doc) |  | ||||||
|         }, |  | ||||||
|       }) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.setZoom(this.settings.get(SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING)) |     this.setZoom(this.settings.get(SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING)) | ||||||
|     this.documentForm.valueChanges |     this.documentForm.valueChanges | ||||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) |       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||||
|       .subscribe((values) => { |       .subscribe(() => { | ||||||
|         this.error = null |         this.error = null | ||||||
|         Object.assign(this.document, this.mapFormToDoc(values)) |         const docValues = Object.assign({}, this.documentForm.value) | ||||||
|  |         docValues['owner'] = | ||||||
|  |           this.documentForm.get('permissions_form').value['owner'] | ||||||
|  |         docValues['set_permissions'] = | ||||||
|  |           this.documentForm.get('permissions_form').value['set_permissions'] | ||||||
|  |         delete docValues['permissions_form'] | ||||||
|  |         Object.assign(this.document, docValues) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|     if ( |     if ( | ||||||
| @@ -536,37 +391,172 @@ export class DocumentDetailComponent | |||||||
|  |  | ||||||
|     this.route.paramMap |     this.route.paramMap | ||||||
|       .pipe( |       .pipe( | ||||||
|         filter( |         filter((paramMap) => { | ||||||
|           (paramMap) => |           // only init when changing docs & section is set | ||||||
|  |           return ( | ||||||
|             +paramMap.get('id') !== this.documentId && |             +paramMap.get('id') !== this.documentId && | ||||||
|             paramMap.get('section')?.length > 0 |             paramMap.get('section')?.length > 0 | ||||||
|         ), |  | ||||||
|         takeUntil(this.unsubscribeNotifier) |  | ||||||
|       ) |  | ||||||
|       .subscribe((paramMap) => { |  | ||||||
|         const documentId = +paramMap.get('id') |  | ||||||
|         this.docChangeNotifier.next(documentId) |  | ||||||
|         this.loadDocument(documentId) |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|     this.route.paramMap |  | ||||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) |  | ||||||
|       .subscribe((paramMap) => { |  | ||||||
|         const section = paramMap.get('section') |  | ||||||
|         if (section) { |  | ||||||
|           const navIDKey: string = Object.keys(DocumentDetailNavIDs).find( |  | ||||||
|             (navID) => navID.toLowerCase() == section |  | ||||||
|           ) |           ) | ||||||
|           if (navIDKey) { |         }), | ||||||
|             this.activeNavID = DocumentDetailNavIDs[navIDKey] |         takeUntil(this.unsubscribeNotifier), | ||||||
|  |         switchMap((paramMap) => { | ||||||
|  |           const documentId = +paramMap.get('id') | ||||||
|  |           this.docChangeNotifier.next(documentId) | ||||||
|  |           // Dont wait to get the preview | ||||||
|  |           this.previewUrl = this.documentsService.getPreviewUrl(documentId) | ||||||
|  |           this.http.get(this.previewUrl, { responseType: 'text' }).subscribe({ | ||||||
|  |             next: (res) => { | ||||||
|  |               this.previewText = res.toString() | ||||||
|  |             }, | ||||||
|  |             error: (err) => { | ||||||
|  |               this.previewText = $localize`An error occurred loading content: ${ | ||||||
|  |                 err.message ?? err.toString() | ||||||
|  |               }` | ||||||
|  |             }, | ||||||
|  |           }) | ||||||
|  |           this.thumbUrl = this.documentsService.getThumbUrl(documentId) | ||||||
|  |           return this.documentsService.get(documentId) | ||||||
|  |         }) | ||||||
|  |       ) | ||||||
|  |       .pipe( | ||||||
|  |         switchMap((doc) => { | ||||||
|  |           this.documentId = doc.id | ||||||
|  |           this.suggestions = null | ||||||
|  |           const openDocument = this.openDocumentService.getOpenDocument( | ||||||
|  |             this.documentId | ||||||
|  |           ) | ||||||
|  |  | ||||||
|  |           if (openDocument) { | ||||||
|  |             if ( | ||||||
|  |               new Date(doc.modified) > new Date(openDocument.modified) && | ||||||
|  |               !this.modalService.hasOpenModals() | ||||||
|  |             ) { | ||||||
|  |               let modal = this.modalService.open(ConfirmDialogComponent) | ||||||
|  |               modal.componentInstance.title = $localize`Document changes detected` | ||||||
|  |               modal.componentInstance.messageBold = $localize`The version of this document in your browser session appears older than the existing version.` | ||||||
|  |               modal.componentInstance.message = $localize`Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.` | ||||||
|  |               modal.componentInstance.cancelBtnClass = 'visually-hidden' | ||||||
|  |               modal.componentInstance.btnCaption = $localize`Ok` | ||||||
|  |               modal.componentInstance.confirmClicked.subscribe(() => | ||||||
|  |                 modal.close() | ||||||
|  |               ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Prevent mutating stale form values into the next document: only sync if it still matches the active document. | ||||||
|  |             if ( | ||||||
|  |               this.documentForm.dirty && | ||||||
|  |               (this.document?.id === openDocument.id || !this.document) | ||||||
|  |             ) { | ||||||
|  |               Object.assign(openDocument, this.documentForm.value) | ||||||
|  |               openDocument['owner'] = | ||||||
|  |                 this.documentForm.get('permissions_form').value['owner'] | ||||||
|  |               openDocument['permissions'] = | ||||||
|  |                 this.documentForm.get('permissions_form').value[ | ||||||
|  |                   'set_permissions' | ||||||
|  |                 ] | ||||||
|  |               delete openDocument['permissions_form'] | ||||||
|  |             } | ||||||
|  |             if (openDocument.__changedFields) { | ||||||
|  |               openDocument.__changedFields.forEach((field) => { | ||||||
|  |                 if (field === 'owner' || field === 'set_permissions') { | ||||||
|  |                   this.documentForm.get('permissions_form').markAsDirty() | ||||||
|  |                 } else { | ||||||
|  |                   this.documentForm.get(field)?.markAsDirty() | ||||||
|  |                 } | ||||||
|  |               }) | ||||||
|  |             } | ||||||
|  |             this.updateComponent(openDocument) | ||||||
|  |           } else { | ||||||
|  |             this.openDocumentService.openDocument(doc) | ||||||
|  |             this.updateComponent(doc) | ||||||
|           } |           } | ||||||
|         } else if (paramMap.get('id')) { |  | ||||||
|           this.router.navigate(['documents', +paramMap.get('id'), 'details'], { |           this.titleSubject | ||||||
|  |             .pipe( | ||||||
|  |               debounceTime(1000), | ||||||
|  |               distinctUntilChanged(), | ||||||
|  |               takeUntil(this.docChangeNotifier), | ||||||
|  |               takeUntil(this.unsubscribeNotifier) | ||||||
|  |             ) | ||||||
|  |             .subscribe({ | ||||||
|  |               next: (titleValue) => { | ||||||
|  |                 // In the rare case when the field changed just after debounced event was fired. | ||||||
|  |                 // We dont want to overwrite what's actually in the text field, so just return | ||||||
|  |                 if (titleValue !== this.titleInput.value) return | ||||||
|  |  | ||||||
|  |                 this.title = titleValue | ||||||
|  |                 this.documentForm.patchValue({ title: titleValue }) | ||||||
|  |               }, | ||||||
|  |               complete: () => { | ||||||
|  |                 // doc changed so we manually check dirty in case title was changed | ||||||
|  |                 if ( | ||||||
|  |                   this.store.getValue().title !== | ||||||
|  |                   this.documentForm.get('title').value | ||||||
|  |                 ) { | ||||||
|  |                   this.openDocumentService.setDirty( | ||||||
|  |                     doc, | ||||||
|  |                     true, | ||||||
|  |                     this.getChangedFields() | ||||||
|  |                   ) | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |             }) | ||||||
|  |  | ||||||
|  |           // Initialize dirtyCheck | ||||||
|  |           this.store = new BehaviorSubject({ | ||||||
|  |             title: doc.title, | ||||||
|  |             content: doc.content, | ||||||
|  |             created: doc.created, | ||||||
|  |             correspondent: doc.correspondent, | ||||||
|  |             document_type: doc.document_type, | ||||||
|  |             storage_path: doc.storage_path, | ||||||
|  |             archive_serial_number: doc.archive_serial_number, | ||||||
|  |             tags: [...doc.tags], | ||||||
|  |             permissions_form: { | ||||||
|  |               owner: doc.owner, | ||||||
|  |               set_permissions: doc.permissions, | ||||||
|  |             }, | ||||||
|  |             custom_fields: [...doc.custom_fields], | ||||||
|  |           }) | ||||||
|  |  | ||||||
|  |           this.isDirty$ = dirtyCheck( | ||||||
|  |             this.documentForm, | ||||||
|  |             this.store.asObservable() | ||||||
|  |           ) | ||||||
|  |  | ||||||
|  |           return this.isDirty$.pipe( | ||||||
|  |             takeUntil(this.unsubscribeNotifier), | ||||||
|  |             map((dirty) => ({ doc, dirty })) | ||||||
|  |           ) | ||||||
|  |         }) | ||||||
|  |       ) | ||||||
|  |       .subscribe({ | ||||||
|  |         next: ({ doc, dirty }) => { | ||||||
|  |           this.openDocumentService.setDirty(doc, dirty, this.getChangedFields()) | ||||||
|  |         }, | ||||||
|  |         error: (error) => { | ||||||
|  |           this.router.navigate(['404'], { | ||||||
|             replaceUrl: true, |             replaceUrl: true, | ||||||
|           }) |           }) | ||||||
|         } |         }, | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|  |     this.route.paramMap.subscribe((paramMap) => { | ||||||
|  |       const section = paramMap.get('section') | ||||||
|  |       if (section) { | ||||||
|  |         const navIDKey: string = Object.keys(DocumentDetailNavIDs).find( | ||||||
|  |           (navID) => navID.toLowerCase() == section | ||||||
|  |         ) | ||||||
|  |         if (navIDKey) { | ||||||
|  |           this.activeNavID = DocumentDetailNavIDs[navIDKey] | ||||||
|  |         } | ||||||
|  |       } else if (paramMap.get('id')) { | ||||||
|  |         this.router.navigate(['documents', +paramMap.get('id'), 'details'], { | ||||||
|  |           replaceUrl: true, | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|     this.hotKeyService |     this.hotKeyService | ||||||
|       .addShortcut({ |       .addShortcut({ | ||||||
|         keys: 'control.arrowright', |         keys: 'control.arrowright', | ||||||
| @@ -692,7 +682,19 @@ export class DocumentDetailComponent | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|     this.title = this.documentTitlePipe.transform(doc.title) |     this.title = this.documentTitlePipe.transform(doc.title) | ||||||
|     this.prepareForm(doc) |     const docFormValues = Object.assign({}, doc) | ||||||
|  |     docFormValues['permissions_form'] = { | ||||||
|  |       owner: doc.owner, | ||||||
|  |       set_permissions: doc.permissions, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.documentForm.patchValue(docFormValues, { emitEvent: false }) | ||||||
|  |     if (!this.userCanEdit) this.documentForm.disable() | ||||||
|  |     setTimeout(() => { | ||||||
|  |       // check again after a tick in case form was dirty | ||||||
|  |       if (!this.userCanEdit) this.documentForm.disable() | ||||||
|  |       else this.documentForm.enable() | ||||||
|  |     }, 10) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get customFieldFormFields(): FormArray { |   get customFieldFormFields(): FormArray { | ||||||
| @@ -795,11 +797,7 @@ export class DocumentDetailComponent | |||||||
|   discard() { |   discard() { | ||||||
|     this.documentsService |     this.documentsService | ||||||
|       .get(this.documentId) |       .get(this.documentId) | ||||||
|       .pipe( |       .pipe(first()) | ||||||
|         first(), |  | ||||||
|         takeUntil(this.unsubscribeNotifier), |  | ||||||
|         takeUntil(this.docChangeNotifier) |  | ||||||
|       ) |  | ||||||
|       .subscribe({ |       .subscribe({ | ||||||
|         next: (doc) => { |         next: (doc) => { | ||||||
|           Object.assign(this.document, doc) |           Object.assign(this.document, doc) | ||||||
| @@ -902,10 +900,9 @@ export class DocumentDetailComponent | |||||||
|       .patch(this.getChangedFields()) |       .patch(this.getChangedFields()) | ||||||
|       .pipe( |       .pipe( | ||||||
|         switchMap((updateResult) => { |         switchMap((updateResult) => { | ||||||
|           return this.documentListViewService.getNext(this.documentId).pipe( |           return this.documentListViewService | ||||||
|             map((nextDocId) => ({ nextDocId, updateResult })), |             .getNext(this.documentId) | ||||||
|             takeUntil(this.unsubscribeNotifier) |             .pipe(map((nextDocId) => ({ nextDocId, updateResult }))) | ||||||
|           ) |  | ||||||
|         }) |         }) | ||||||
|       ) |       ) | ||||||
|       .pipe( |       .pipe( | ||||||
| @@ -915,10 +912,7 @@ export class DocumentDetailComponent | |||||||
|             return this.openDocumentService |             return this.openDocumentService | ||||||
|               .closeDocument(this.document) |               .closeDocument(this.document) | ||||||
|               .pipe( |               .pipe( | ||||||
|                 map( |                 map((closeResult) => ({ updateResult, nextDocId, closeResult })) | ||||||
|                   (closeResult) => ({ updateResult, nextDocId, closeResult }), |  | ||||||
|                   takeUntil(this.unsubscribeNotifier) |  | ||||||
|                 ) |  | ||||||
|               ) |               ) | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
| @@ -1244,19 +1238,16 @@ export class DocumentDetailComponent | |||||||
|     ) { |     ) { | ||||||
|       doc.owner = this.store.value.permissions_form.owner |       doc.owner = this.store.value.permissions_form.owner | ||||||
|     } |     } | ||||||
|     return !this.document || this.userCanEditDoc(doc) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private userCanEditDoc(doc: Document): boolean { |  | ||||||
|     return ( |     return ( | ||||||
|       this.permissionsService.currentUserCan( |       !this.document || | ||||||
|  |       (this.permissionsService.currentUserCan( | ||||||
|         PermissionAction.Change, |         PermissionAction.Change, | ||||||
|         PermissionType.Document |         PermissionType.Document | ||||||
|       ) && |       ) && | ||||||
|       this.permissionsService.currentUserHasObjectPermissions( |         this.permissionsService.currentUserHasObjectPermissions( | ||||||
|         PermissionAction.Change, |           PermissionAction.Change, | ||||||
|         doc |           doc | ||||||
|       ) |         )) | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1438,50 +1429,43 @@ export class DocumentDetailComponent | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private tryRenderTiff() { |   private tryRenderTiff() { | ||||||
|     this.http |     this.http.get(this.previewUrl, { responseType: 'arraybuffer' }).subscribe({ | ||||||
|       .get(this.previewUrl, { responseType: 'arraybuffer' }) |       next: (res) => { | ||||||
|       .pipe( |         /* istanbul ignore next */ | ||||||
|         first(), |         try { | ||||||
|         takeUntil(this.unsubscribeNotifier), |           // See UTIF.js > _imgLoaded | ||||||
|         takeUntil(this.docChangeNotifier) |           const tiffIfds: any[] = UTIF.decode(res) | ||||||
|       ) |           var vsns = tiffIfds, | ||||||
|       .subscribe({ |             ma = 0, | ||||||
|         next: (res) => { |             page = vsns[0] | ||||||
|           /* istanbul ignore next */ |           if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD) | ||||||
|           try { |           for (var i = 0; i < vsns.length; i++) { | ||||||
|             // See UTIF.js > _imgLoaded |             var img = vsns[i] | ||||||
|             const tiffIfds: any[] = UTIF.decode(res) |             if (img['t258'] == null || img['t258'].length < 3) continue | ||||||
|             var vsns = tiffIfds, |             var ar = img['t256'] * img['t257'] | ||||||
|               ma = 0, |             if (ar > ma) { | ||||||
|               page = vsns[0] |               ma = ar | ||||||
|             if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD) |               page = img | ||||||
|             for (var i = 0; i < vsns.length; i++) { |  | ||||||
|               var img = vsns[i] |  | ||||||
|               if (img['t258'] == null || img['t258'].length < 3) continue |  | ||||||
|               var ar = img['t256'] * img['t257'] |  | ||||||
|               if (ar > ma) { |  | ||||||
|                 ma = ar |  | ||||||
|                 page = img |  | ||||||
|               } |  | ||||||
|             } |             } | ||||||
|             UTIF.decodeImage(res, page, tiffIfds) |  | ||||||
|             const rgba = UTIF.toRGBA8(page) |  | ||||||
|             const { width: w, height: h } = page |  | ||||||
|             var cnv = document.createElement('canvas') |  | ||||||
|             cnv.width = w |  | ||||||
|             cnv.height = h |  | ||||||
|             var ctx = cnv.getContext('2d'), |  | ||||||
|               imgd = ctx.createImageData(w, h) |  | ||||||
|             for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i] |  | ||||||
|             ctx.putImageData(imgd, 0, 0) |  | ||||||
|             this.tiffURL = cnv.toDataURL() |  | ||||||
|           } catch (err) { |  | ||||||
|             this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}` |  | ||||||
|           } |           } | ||||||
|         }, |           UTIF.decodeImage(res, page, tiffIfds) | ||||||
|         error: (err) => { |           const rgba = UTIF.toRGBA8(page) | ||||||
|  |           const { width: w, height: h } = page | ||||||
|  |           var cnv = document.createElement('canvas') | ||||||
|  |           cnv.width = w | ||||||
|  |           cnv.height = h | ||||||
|  |           var ctx = cnv.getContext('2d'), | ||||||
|  |             imgd = ctx.createImageData(w, h) | ||||||
|  |           for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i] | ||||||
|  |           ctx.putImageData(imgd, 0, 0) | ||||||
|  |           this.tiffURL = cnv.toDataURL() | ||||||
|  |         } catch (err) { | ||||||
|           this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}` |           this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}` | ||||||
|         }, |         } | ||||||
|       }) |       }, | ||||||
|  |       error: (err) => { | ||||||
|  |         this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}` | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,8 +15,13 @@ | |||||||
|       } |       } | ||||||
|  |  | ||||||
|     </div> |     </div> | ||||||
|     <div class="col col-md-10"> |   <div class="col col-md-10"> | ||||||
|       <div class="card-body"> |       <div class="card-body"> | ||||||
|  |         @if (document?.in_process) { | ||||||
|  |           <span class="badge bg-secondary text-light mb-2"> | ||||||
|  |             <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |           </span> | ||||||
|  |         } | ||||||
|         <div class="d-flex justify-content-between align-items-center"> |         <div class="d-flex justify-content-between align-items-center"> | ||||||
|           <h5 class="card-title w-100"> |           <h5 class="card-title w-100"> | ||||||
|             @if (document) { |             @if (document) { | ||||||
|   | |||||||
| @@ -37,6 +37,11 @@ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     <div class="card-body bg-light p-2"> |     <div class="card-body bg-light p-2"> | ||||||
|  |       @if (document?.in_process) { | ||||||
|  |         <span class="badge bg-secondary text-light mb-2"> | ||||||
|  |           <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |         </span> | ||||||
|  |       } | ||||||
|       <p class="card-text"> |       <p class="card-text"> | ||||||
|         @if (document) { |         @if (document) { | ||||||
|           @if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) { |           @if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) { | ||||||
|   | |||||||
| @@ -301,6 +301,11 @@ | |||||||
|                 } |                 } | ||||||
|                 @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) { |                 @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) { | ||||||
|                   <td width="30%"> |                   <td width="30%"> | ||||||
|  |                     @if (d.in_process) { | ||||||
|  |                       <span class="badge bg-secondary text-light me-1"> | ||||||
|  |                         <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |                       </span> | ||||||
|  |                     } | ||||||
|                     @if (activeDisplayFields.includes(DisplayField.TITLE)) { |                     @if (activeDisplayFields.includes(DisplayField.TITLE)) { | ||||||
|                       <div class="d-inline-block" (mouseleave)="popupPreview.close()"> |                       <div class="d-inline-block" (mouseleave)="popupPreview.close()"> | ||||||
|                         <a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a> |                         <a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a> | ||||||
|   | |||||||
| @@ -199,14 +199,6 @@ describe('DocumentListComponent', () => { | |||||||
|     } |     } | ||||||
|     const queryParams = { id: view.id.toString() } |     const queryParams = { id: view.id.toString() } | ||||||
|     const getSavedViewSpy = jest.spyOn(savedViewService, 'getCached') |     const getSavedViewSpy = jest.spyOn(savedViewService, 'getCached') | ||||||
|     const setCountSpy = jest.spyOn(savedViewService, 'setDocumentCount') |  | ||||||
|     jest.spyOn(documentService, 'listFiltered').mockReturnValue( |  | ||||||
|       of({ |  | ||||||
|         results: docs, |  | ||||||
|         count: 3, |  | ||||||
|         all: docs.map((d) => d.id), |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|     getSavedViewSpy.mockReturnValue(of(view)) |     getSavedViewSpy.mockReturnValue(of(view)) | ||||||
|     const activateSavedViewSpy = jest.spyOn( |     const activateSavedViewSpy = jest.spyOn( | ||||||
|       documentListService, |       documentListService, | ||||||
| @@ -223,7 +215,6 @@ describe('DocumentListComponent', () => { | |||||||
|       view, |       view, | ||||||
|       convertToParamMap(queryParams) |       convertToParamMap(queryParams) | ||||||
|     ) |     ) | ||||||
|     expect(setCountSpy).toHaveBeenCalledWith(view, 3) |  | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   it('should 404 on load saved view from URL if no view', () => { |   it('should 404 on load saved view from URL if no view', () => { | ||||||
| @@ -257,34 +248,6 @@ describe('DocumentListComponent', () => { | |||||||
|     expect(getSavedViewSpy).toHaveBeenCalledWith(view.id) |     expect(getSavedViewSpy).toHaveBeenCalledWith(view.id) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   it('should update saved view document count on load saved view from query params', () => { |  | ||||||
|     jest.spyOn(savedViewService, 'getCached').mockReturnValue( |  | ||||||
|       of({ |  | ||||||
|         id: 10, |  | ||||||
|         sort_field: 'added', |  | ||||||
|         sort_reverse: true, |  | ||||||
|         filter_rules: [], |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|     jest.spyOn(documentService, 'listFiltered').mockReturnValue( |  | ||||||
|       of({ |  | ||||||
|         results: docs, |  | ||||||
|         count: 3, |  | ||||||
|         all: docs.map((d) => d.id), |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|     const setCountSpy = jest.spyOn(savedViewService, 'setDocumentCount') |  | ||||||
|     jest.spyOn(documentService, 'listFiltered').mockReturnValue( |  | ||||||
|       of({ |  | ||||||
|         results: docs, |  | ||||||
|         count: 3, |  | ||||||
|         all: docs.map((d) => d.id), |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|     component.loadViewConfig(10) |  | ||||||
|     expect(setCountSpy).toHaveBeenCalledWith(expect.any(Object), 3) |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   it('should support 3 different display modes', () => { |   it('should support 3 different display modes', () => { | ||||||
|     jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs) |     jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs) | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|   | |||||||
| @@ -264,9 +264,7 @@ export class DocumentListComponent | |||||||
|           view, |           view, | ||||||
|           convertToParamMap(this.route.snapshot.queryParams) |           convertToParamMap(this.route.snapshot.queryParams) | ||||||
|         ) |         ) | ||||||
|         this.list.reload(() => { |         this.list.reload() | ||||||
|           this.savedViewService.setDocumentCount(view, this.list.collectionSize) |  | ||||||
|         }) |  | ||||||
|         this.updateDisplayCustomFields() |         this.updateDisplayCustomFields() | ||||||
|         this.unmodifiedFilterRules = view.filter_rules |         this.unmodifiedFilterRules = view.filter_rules | ||||||
|       }) |       }) | ||||||
| @@ -401,9 +399,7 @@ export class DocumentListComponent | |||||||
|       .subscribe((view) => { |       .subscribe((view) => { | ||||||
|         this.unmodifiedSavedView = view |         this.unmodifiedSavedView = view | ||||||
|         this.list.activateSavedView(view) |         this.list.activateSavedView(view) | ||||||
|         this.list.reload(() => { |         this.list.reload() | ||||||
|           this.savedViewService.setDocumentCount(view, this.list.collectionSize) |  | ||||||
|         }) |  | ||||||
|       }) |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -159,6 +159,8 @@ export interface Document extends ObjectWithPermissions { | |||||||
|  |  | ||||||
|   page_count?: number |   page_count?: number | ||||||
|  |  | ||||||
|  |   in_process?: boolean | ||||||
|  |  | ||||||
|   // Frontend only |   // Frontend only | ||||||
|   __changedFields?: string[] |   __changedFields?: string[] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -140,15 +140,11 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> { | |||||||
|         ) |         ) | ||||||
|         .pipe(takeUntil(this.unsubscribeNotifier)) |         .pipe(takeUntil(this.unsubscribeNotifier)) | ||||||
|         .subscribe((results: Results<Document>) => { |         .subscribe((results: Results<Document>) => { | ||||||
|           this.setDocumentCount(view, results.count) |           this.savedViewDocumentCounts.set(view.id, results.count) | ||||||
|         }) |         }) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public setDocumentCount(view: SavedView, count: number) { |  | ||||||
|     this.savedViewDocumentCounts.set(view.id, count) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public getDocumentCount(view: SavedView): number { |   public getDocumentCount(view: SavedView): number { | ||||||
|     return this.savedViewDocumentCounts.get(view.id) |     return this.savedViewDocumentCounts.get(view.id) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -283,6 +283,7 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]: | |||||||
|         f"Attempting to rotate {len(doc_ids)} documents by {degrees} degrees.", |         f"Attempting to rotate {len(doc_ids)} documents by {degrees} degrees.", | ||||||
|     ) |     ) | ||||||
|     qs = Document.objects.filter(id__in=doc_ids) |     qs = Document.objects.filter(id__in=doc_ids) | ||||||
|  |     Document.objects.filter(pk__in=doc_ids).update(in_process=True) | ||||||
|     affected_docs: list[int] = [] |     affected_docs: list[int] = [] | ||||||
|     import pikepdf |     import pikepdf | ||||||
|  |  | ||||||
| @@ -309,7 +310,9 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]: | |||||||
|                     f"Rotated document {doc.id} by {degrees} degrees", |                     f"Rotated document {doc.id} by {degrees} degrees", | ||||||
|                 ) |                 ) | ||||||
|                 affected_docs.append(doc.id) |                 affected_docs.append(doc.id) | ||||||
|  |                 Document.objects.filter(pk__in=doc_ids).update(in_process=False) | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|  |             Document.objects.filter(pk__in=doc_ids).update(in_process=False) | ||||||
|             logger.exception(f"Error rotating document {doc.id}: {e}") |             logger.exception(f"Error rotating document {doc.id}: {e}") | ||||||
|  |  | ||||||
|     if len(affected_docs) > 0: |     if len(affected_docs) > 0: | ||||||
| @@ -474,6 +477,7 @@ def delete_pages(doc_ids: list[int], pages: list[int]) -> Literal["OK"]: | |||||||
|         f"Attempting to delete pages {pages} from {len(doc_ids)} documents", |         f"Attempting to delete pages {pages} from {len(doc_ids)} documents", | ||||||
|     ) |     ) | ||||||
|     doc = Document.objects.get(id=doc_ids[0]) |     doc = Document.objects.get(id=doc_ids[0]) | ||||||
|  |     Document.objects.filter(pk=doc.id).update(in_process=True) | ||||||
|     pages = sorted(pages)  # sort pages to avoid index issues |     pages = sorted(pages)  # sort pages to avoid index issues | ||||||
|     import pikepdf |     import pikepdf | ||||||
|  |  | ||||||
| @@ -492,6 +496,7 @@ def delete_pages(doc_ids: list[int], pages: list[int]) -> Literal["OK"]: | |||||||
|             update_document_content_maybe_archive_file.delay(document_id=doc.id) |             update_document_content_maybe_archive_file.delay(document_id=doc.id) | ||||||
|             logger.info(f"Deleted pages {pages} from document {doc.id}") |             logger.info(f"Deleted pages {pages} from document {doc.id}") | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|  |         Document.objects.filter(pk=doc.id).update(in_process=False) | ||||||
|         logger.exception(f"Error deleting pages from document {doc.id}: {e}") |         logger.exception(f"Error deleting pages from document {doc.id}: {e}") | ||||||
|  |  | ||||||
|     return "OK" |     return "OK" | ||||||
| @@ -518,6 +523,7 @@ def edit_pdf( | |||||||
|         f"Editing PDF of document {doc_ids[0]} with {len(operations)} operations", |         f"Editing PDF of document {doc_ids[0]} with {len(operations)} operations", | ||||||
|     ) |     ) | ||||||
|     doc = Document.objects.get(id=doc_ids[0]) |     doc = Document.objects.get(id=doc_ids[0]) | ||||||
|  |     Document.objects.filter(pk=doc.id).update(in_process=True) | ||||||
|     import pikepdf |     import pikepdf | ||||||
|  |  | ||||||
|     pdf_docs: list[pikepdf.Pdf] = [] |     pdf_docs: list[pikepdf.Pdf] = [] | ||||||
| @@ -587,6 +593,7 @@ def edit_pdf( | |||||||
|  |  | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         logger.exception(f"Error editing document {doc.id}: {e}") |         logger.exception(f"Error editing document {doc.id}: {e}") | ||||||
|  |         Document.objects.filter(pk=doc.id).update(in_process=False) | ||||||
|         raise ValueError( |         raise ValueError( | ||||||
|             f"An error occurred while editing the document: {e}", |             f"An error occurred while editing the document: {e}", | ||||||
|         ) from e |         ) from e | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/documents/migrations/1069_document_in_process.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/documents/migrations/1069_document_in_process.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | # Generated by Django 5.2.5 on 2025-08-26 07:54 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  | from django.db import models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |     dependencies = [ | ||||||
|  |         ("documents", "1068_alter_document_created"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="document", | ||||||
|  |             name="in_process", | ||||||
|  |             field=models.BooleanField( | ||||||
|  |                 db_index=True, | ||||||
|  |                 default=False, | ||||||
|  |                 help_text="Whether the document is currently being processed.", | ||||||
|  |                 verbose_name="in process", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -289,6 +289,13 @@ class Document(SoftDeleteModel, ModelWithOwner): | |||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     in_process = models.BooleanField( | ||||||
|  |         _("in process"), | ||||||
|  |         default=False, | ||||||
|  |         db_index=True, | ||||||
|  |         help_text=_("Whether the document is currently being processed."), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ("-created",) |         ordering = ("-created",) | ||||||
|         verbose_name = _("document") |         verbose_name = _("document") | ||||||
|   | |||||||
| @@ -935,6 +935,8 @@ class DocumentSerializer( | |||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     in_process = serializers.BooleanField(read_only=True) | ||||||
|  |  | ||||||
|     def get_page_count(self, obj) -> int | None: |     def get_page_count(self, obj) -> int | None: | ||||||
|         return obj.page_count |         return obj.page_count | ||||||
|  |  | ||||||
| @@ -1103,6 +1105,7 @@ class DocumentSerializer( | |||||||
|             "remove_inbox_tags", |             "remove_inbox_tags", | ||||||
|             "page_count", |             "page_count", | ||||||
|             "mime_type", |             "mime_type", | ||||||
|  |             "in_process", | ||||||
|         ) |         ) | ||||||
|         list_serializer_class = OwnedObjectListSerializer |         list_serializer_class = OwnedObjectListSerializer | ||||||
|  |  | ||||||
|   | |||||||
| @@ -250,6 +250,7 @@ def update_document_content_maybe_archive_file(document_id): | |||||||
|     it exists. |     it exists. | ||||||
|     """ |     """ | ||||||
|     document = Document.objects.get(id=document_id) |     document = Document.objects.get(id=document_id) | ||||||
|  |     Document.objects.filter(pk=document_id).update(in_process=True) | ||||||
|  |  | ||||||
|     mime_type = document.mime_type |     mime_type = document.mime_type | ||||||
|  |  | ||||||
| @@ -349,6 +350,7 @@ def update_document_content_maybe_archive_file(document_id): | |||||||
|         ) |         ) | ||||||
|     finally: |     finally: | ||||||
|         parser.cleanup() |         parser.cleanup() | ||||||
|  |         Document.objects.filter(pk=document_id).update(in_process=False) | ||||||
|  |  | ||||||
|  |  | ||||||
| @shared_task | @shared_task | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user