Compare commits

...

276 Commits

Author SHA1 Message Date
Michael Shamoon
f9ce4d8f6a Update version strings for 1.11.0 2022-12-29 19:40:25 -08:00
Michael Shamoon
8c9a74ee0c Merge branch 'dev' 2022-12-29 19:39:38 -08:00
shamoon
0b59ef2cfa Merge pull request #2117 from paperless-ngx/l10n_dev
New Crowdin updates
2022-12-29 19:38:55 -08:00
shamoon
0099631905 Merge pull request #2262 from paperless-ngx/feature-test-downgrade
Chore: Downgrade hiredis to 2.0.0
2022-12-29 18:26:25 -08:00
Trenton H
4548038525 Downgrades hiredis until pihweels can build it 2022-12-29 17:27:54 -08:00
Trenton H
a2b7687c3b In the case of an RTL language being extracted via pdfminer.six, fall back to forced OCR, which handles RTL text better 2022-12-29 16:02:02 -08:00
Trenton H
15cba8e14d Adds a notable changes section for release changelogs 2022-12-29 15:41:45 -08:00
shamoon
605f86f0cf Merge pull request #2256 from mendelk/patch-1
Fixed typo in docs
2022-12-28 15:55:35 -08:00
Mendel Kramer
8cbaca22c1 Fixed typo in docs 2022-12-28 18:16:00 -05:00
hashworks
4269074944 Add ability to provide the configuration file path using an env variable 2022-12-28 13:48:01 -08:00
Paperless-ngx Translation Bot [bot]
7b7331683d New translations messages.xlf (French)
[ci skip]
2022-12-28 10:39:37 -08:00
Paperless-ngx Translation Bot [bot]
a83637b2bf New translations messages.xlf (French)
[ci skip]
2022-12-28 09:06:23 -08:00
Paperless-ngx Translation Bot [bot]
721447999e New translations messages.xlf (French)
[ci skip]
2022-12-28 07:58:54 -08:00
Paperless-ngx Translation Bot [bot]
72cbdca6e8 New translations messages.xlf (German)
[ci skip]
2022-12-28 04:16:31 -08:00
Paperless-ngx Translation Bot [bot]
22e060e00e New translations messages.xlf (German)
[ci skip]
2022-12-28 02:13:21 -08:00
Paperless-ngx Translation Bot [bot]
23fb5c2a1f New translations messages.xlf (German)
[ci skip]
2022-12-28 01:05:51 -08:00
Trenton Holmes
d6e6f49c15 Updates Python libraries and hooks to most recent versions 2022-12-27 08:50:13 -08:00
Paperless-ngx Translation Bot [bot]
fc259c8bfd New translations messages.xlf (Arabic)
[ci skip]
2022-12-27 01:57:54 -08:00
Paperless-ngx Translation Bot [bot]
ecf90c4718 New translations django.po (French)
[ci skip]
2022-12-25 04:32:28 -08:00
Paperless-ngx Translation Bot [bot]
f0b359889e New translations django.po (French)
[ci skip]
2022-12-25 03:34:08 -08:00
tooomm
b0fb44db86 check repo owner 2022-12-23 18:46:52 -08:00
Paperless-ngx Translation Bot [bot]
bfd955b210 New translations messages.xlf (Serbian (Latin))
[ci skip]
2022-12-23 17:23:28 -08:00
Paperless-ngx Translation Bot [bot]
8af21d6fe3 New translations messages.xlf (Luxembourgish)
[ci skip]
2022-12-23 17:23:27 -08:00
Paperless-ngx Translation Bot [bot]
e9f25190e9 New translations messages.xlf (Croatian)
[ci skip]
2022-12-23 17:23:25 -08:00
Paperless-ngx Translation Bot [bot]
e81b829eb0 New translations messages.xlf (Portuguese, Brazilian)
[ci skip]
2022-12-23 17:23:24 -08:00
Paperless-ngx Translation Bot [bot]
5f4e5c2cfb New translations messages.xlf (Chinese Simplified)
[ci skip]
2022-12-23 17:23:23 -08:00
Paperless-ngx Translation Bot [bot]
383358376f New translations messages.xlf (Turkish)
[ci skip]
2022-12-23 17:23:22 -08:00
Paperless-ngx Translation Bot [bot]
00f0b55729 New translations messages.xlf (Swedish)
[ci skip]
2022-12-23 17:23:21 -08:00
Paperless-ngx Translation Bot [bot]
b10b981cb5 New translations messages.xlf (Slovenian)
[ci skip]
2022-12-23 17:23:19 -08:00
Paperless-ngx Translation Bot [bot]
f805407bce New translations messages.xlf (Russian)
[ci skip]
2022-12-23 17:23:18 -08:00
Paperless-ngx Translation Bot [bot]
4f169da4a8 New translations messages.xlf (Portuguese)
[ci skip]
2022-12-23 17:23:17 -08:00
Paperless-ngx Translation Bot [bot]
4031381c31 New translations messages.xlf (Polish)
[ci skip]
2022-12-23 17:23:16 -08:00
Paperless-ngx Translation Bot [bot]
b7bc3830cc New translations messages.xlf (Norwegian)
[ci skip]
2022-12-23 17:23:15 -08:00
Paperless-ngx Translation Bot [bot]
f2872d6475 New translations messages.xlf (Dutch)
[ci skip]
2022-12-23 17:23:14 -08:00
Paperless-ngx Translation Bot [bot]
f5219c101c New translations messages.xlf (Italian)
[ci skip]
2022-12-23 17:23:12 -08:00
Paperless-ngx Translation Bot [bot]
f329b5a3d0 New translations messages.xlf (Hebrew)
[ci skip]
2022-12-23 17:23:11 -08:00
Paperless-ngx Translation Bot [bot]
6f6a5f2eed New translations messages.xlf (Finnish)
[ci skip]
2022-12-23 17:23:10 -08:00
Paperless-ngx Translation Bot [bot]
266a8cd1a9 New translations messages.xlf (Danish)
[ci skip]
2022-12-23 17:23:09 -08:00
Paperless-ngx Translation Bot [bot]
7675014c90 New translations messages.xlf (Czech)
[ci skip]
2022-12-23 17:23:08 -08:00
Paperless-ngx Translation Bot [bot]
ec5971c134 New translations messages.xlf (Belarusian)
[ci skip]
2022-12-23 17:23:07 -08:00
Paperless-ngx Translation Bot [bot]
d7fedfcd87 New translations messages.xlf (Arabic)
[ci skip]
2022-12-23 17:23:05 -08:00
Paperless-ngx Translation Bot [bot]
cb99a8741e New translations messages.xlf (Spanish)
[ci skip]
2022-12-23 17:23:04 -08:00
Paperless-ngx Translation Bot [bot]
a63ed236a4 New translations messages.xlf (French)
[ci skip]
2022-12-23 17:23:03 -08:00
Paperless-ngx Translation Bot [bot]
4594a5c41c New translations messages.xlf (Romanian)
[ci skip]
2022-12-23 17:23:02 -08:00
Paperless-ngx Translation Bot [bot]
9109c25b3e New translations messages.xlf (German)
[ci skip]
2022-12-23 17:23:00 -08:00
shamoon
5435dc2499 Merge pull request #2205 from paperless-ngx/fix-2204
Fix: support `tags__id__none` in advanced search, fix tags filter badge count for excluded
2022-12-23 16:27:50 -08:00
Michael Shamoon
1d300fafad Fix missing word in settings string 2022-12-23 16:27:19 -08:00
Paperless-ngx Translation Bot [bot]
b1194f9524 New translations messages.xlf (German)
[ci skip]
2022-12-23 14:50:15 -08:00
Paperless-ngx Translation Bot [bot]
2532bd1e2c New translations django.po (Russian)
[ci skip]
2022-12-21 03:37:55 -08:00
ThellraAK
800e842ab3 Removing Mariadb default open port (#2227)
* Removing Mariadb default open port

Removing the listening port 3306 for the DB, Docker networks will let the containers talk to one another.  The existing setup would allow anyone to connect to the DB and use the default passwords.

* Update docker-compose.mariadb-tika.yml

Adding change to the other compose file to remove open port

* Remove excess blank lines

* Remove excess blank lines

Co-authored-by: Felix E <felix@eckhofer.com>
2022-12-21 02:36:37 -08:00
shamoon
6f6f365e2b Merge pull request #2203 from tooomm/docs_updates
Docs: More fixes and improvements
2022-12-17 19:58:06 -08:00
tooomm
43b863b816 doc fixes
This reverts commit e015babdc102a65a3cce0cc71812d3eb730da92e.

link fix

fix escaping, spacing, profile links, typo

revert

~~add~~ at fixes

Revert "~~add~~ at fixes"

This reverts commit ce0192b733c19614048de81ea917660e25bb35f2.
2022-12-17 19:55:22 -08:00
Michael Shamoon
94e32005ca support tags__id__none for advanced search 2022-12-17 19:49:17 -08:00
Michael Shamoon
204e14877d fix excluded items not showing in tag dropdown count 2022-12-17 19:49:00 -08:00
Paperless-ngx Translation Bot [bot]
92f05e051f New translations messages.xlf (Serbian (Latin))
[ci skip]
2022-12-16 23:40:36 -08:00
Paperless-ngx Translation Bot [bot]
17bdf2a233 New translations messages.xlf (Luxembourgish)
[ci skip]
2022-12-16 23:40:35 -08:00
Paperless-ngx Translation Bot [bot]
ce37100a0a New translations messages.xlf (Croatian)
[ci skip]
2022-12-16 23:40:33 -08:00
Paperless-ngx Translation Bot [bot]
81c371d66b New translations messages.xlf (Portuguese, Brazilian)
[ci skip]
2022-12-16 23:40:32 -08:00
Paperless-ngx Translation Bot [bot]
c114653977 New translations messages.xlf (Chinese Simplified)
[ci skip]
2022-12-16 23:40:31 -08:00
Paperless-ngx Translation Bot [bot]
6280b9948a New translations messages.xlf (Turkish)
[ci skip]
2022-12-16 23:40:30 -08:00
Paperless-ngx Translation Bot [bot]
3a322a7b33 New translations messages.xlf (Swedish)
[ci skip]
2022-12-16 23:40:29 -08:00
Paperless-ngx Translation Bot [bot]
327ae03589 New translations messages.xlf (Slovenian)
[ci skip]
2022-12-16 23:40:27 -08:00
Paperless-ngx Translation Bot [bot]
cffbea9053 New translations messages.xlf (Russian)
[ci skip]
2022-12-16 23:40:26 -08:00
Paperless-ngx Translation Bot [bot]
ad0ef9a5a8 New translations messages.xlf (Portuguese)
[ci skip]
2022-12-16 23:40:25 -08:00
Paperless-ngx Translation Bot [bot]
03e7299925 New translations messages.xlf (Polish)
[ci skip]
2022-12-16 23:40:24 -08:00
Paperless-ngx Translation Bot [bot]
d3ba910f2d New translations messages.xlf (Norwegian)
[ci skip]
2022-12-16 23:40:22 -08:00
Paperless-ngx Translation Bot [bot]
329e649878 New translations messages.xlf (Dutch)
[ci skip]
2022-12-16 23:40:21 -08:00
Paperless-ngx Translation Bot [bot]
206ee97554 New translations messages.xlf (Italian)
[ci skip]
2022-12-16 23:40:20 -08:00
Paperless-ngx Translation Bot [bot]
e512d4af8c New translations messages.xlf (Hebrew)
[ci skip]
2022-12-16 23:40:19 -08:00
Paperless-ngx Translation Bot [bot]
6a5e752172 New translations messages.xlf (Finnish)
[ci skip]
2022-12-16 23:40:18 -08:00
Paperless-ngx Translation Bot [bot]
6ba527ef55 New translations messages.xlf (Danish)
[ci skip]
2022-12-16 23:40:16 -08:00
Paperless-ngx Translation Bot [bot]
220cc1927c New translations messages.xlf (Czech)
[ci skip]
2022-12-16 23:40:15 -08:00
Paperless-ngx Translation Bot [bot]
9956f4cb47 New translations messages.xlf (Belarusian)
[ci skip]
2022-12-16 23:40:14 -08:00
Paperless-ngx Translation Bot [bot]
fe055f6391 New translations messages.xlf (German)
[ci skip]
2022-12-16 23:40:13 -08:00
Paperless-ngx Translation Bot [bot]
eec506a13c New translations messages.xlf (Arabic)
[ci skip]
2022-12-16 23:40:12 -08:00
Paperless-ngx Translation Bot [bot]
1530bbd1cb New translations messages.xlf (Spanish)
[ci skip]
2022-12-16 23:40:10 -08:00
Paperless-ngx Translation Bot [bot]
db6afdd926 New translations messages.xlf (French)
[ci skip]
2022-12-16 23:40:09 -08:00
Paperless-ngx Translation Bot [bot]
5642715721 New translations messages.xlf (Romanian)
[ci skip]
2022-12-16 23:40:08 -08:00
Michael Shamoon
3bd22f0b0f Update frontend translation strings
[skip ci]
2022-12-16 23:34:13 -08:00
Paperless-ngx Translation Bot [bot]
c92c7e1ced New translations messages.xlf (Serbian (Latin))
[ci skip]
2022-12-16 11:52:09 -08:00
shamoon
940f5d5b50 Merge pull request #2199 from paperless-ngx/eslint
Chore: migrate to eslint
2022-12-16 09:53:59 -08:00
shamoon
4dc893a4fa Merge pull request #2004 from paperless-ngx/feature-export-to-zip
Feature: Adds option to allow a user to export directly to a zipfile
2022-12-16 09:12:38 -08:00
shamoon
6d324dbd8e Update config.yml 2022-12-16 09:10:11 -08:00
shamoon
8ddf05e573 Update bug-report.yml 2022-12-16 09:09:48 -08:00
shamoon
d869a6bcca Merge pull request #2184 from paperless-ngx/feature-paperlesstask-admin
Feature: Adds PaperlessTask admin page interface
2022-12-15 23:50:43 -08:00
Michael Shamoon
40bdeffa38 Add lint to frontend tests 2022-12-15 23:36:01 -08:00
Michael Shamoon
5bf5710d39 lint frontend 2022-12-15 23:36:01 -08:00
Michael Shamoon
551a7e606c Migrate to eslint 2022-12-15 23:36:01 -08:00
Trenton H
feec36939b Improves the manual image building so it now properly gets cache manifests using buildx 2022-12-15 12:22:55 -08:00
Trenton H
554bba839e Skip running system checks before migrate, then run them directly after instead 2022-12-15 11:50:15 -08:00
Trenton H
ebaaa3a1e8 Adds a basic admin view for tasks 2022-12-15 11:31:50 -08:00
Trenton H
bae715cd34 Changes the qpdf building to be cross compiled instead of emulated 2022-12-15 11:15:01 -08:00
Paperless-ngx Translation Bot [bot]
2a3b8f5a7f New translations messages.xlf (German)
[ci skip]
2022-12-15 07:04:25 -08:00
Michael Shamoon
6a023507e2 fix search bar position with slim sidebar
[ci skip]
2022-12-14 23:22:11 -08:00
Trenton H
1551052cde Add piwheels as additional URL for downloading pre-built wheels 2022-12-13 09:58:51 -08:00
Trenton Holmes
b6dd36a439 Notes a TODO for a later library release to remove a workaround 2022-12-12 15:37:01 -08:00
Trenton Holmes
ce38e4ae08 Actually no need for pickle anyway 2022-12-12 15:37:01 -08:00
Trenton H
97d6503fef Switches task serialization over to pickle format 2022-12-12 15:37:01 -08:00
Trenton Holmes
b0625cdced Updates runners to Ubuntu 22.04, locks pipenv to 2022.11.30, reduces number of image cleans run 2022-12-12 06:16:13 -08:00
Paperless-ngx Translation Bot [bot]
31e8c44c18 New translations messages.xlf (Dutch)
[ci skip]
2022-12-11 11:46:22 -08:00
tooomm
0472dfe25a Docs: Fix leftover issues from conversion (#2172) 2022-12-11 10:12:58 -08:00
tooomm
8b36c9ad64 more fixes and cleanup 2022-12-11 08:06:17 -08:00
tooomm
1266f2d5b9 fix links 2022-12-11 08:06:17 -08:00
shamoon
8196051959 Merge pull request #2157 from Weltraumschaf/patch-1
Update setup.md
2022-12-09 16:12:20 -08:00
Sven Strittmatter
d198142a1e Update setup.md
W/o the slash it resolves to /setup/configuration/ which does 404.
2022-12-09 22:09:06 +01:00
shamoon
5e15ede849 Merge pull request #2028 from paperless-ngx/feature-truncate-content
Feature: speed up frontend by truncating content
2022-12-09 09:20:06 -08:00
Michael Shamoon
06a6eb0326 fix code block indentation 2022-12-09 08:15:03 -08:00
shamoon
28819d6d0f Fix code block indentation 2022-12-09 08:11:42 -08:00
Paperless-ngx Translation Bot [bot]
8a9e564dac New translations messages.xlf (Arabic)
[ci skip]
2022-12-09 03:24:52 -08:00
Paperless-ngx Translation Bot [bot]
534704693b New translations django.po (Arabic)
[ci skip]
2022-12-08 23:39:47 -08:00
Paperless-ngx Translation Bot [bot]
bc40607c51 New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 23:39:46 -08:00
Paperless-ngx Translation Bot [bot]
6fdc17cc72 New translations django.po (Arabic)
[ci skip]
2022-12-08 21:57:29 -08:00
Paperless-ngx Translation Bot [bot]
69a5ba0618 New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 21:57:28 -08:00
Paperless-ngx Translation Bot [bot]
3c71a9160f New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 20:57:07 -08:00
Michael Shamoon
48ef8eca80 Add bulk download options dropdown 2022-12-08 18:32:14 -08:00
Trenton Holmes
812df3782a Documents the test conditions 2022-12-08 18:32:14 -08:00
Trenton Holmes
54bb1ae27d Adds an optional API field to follow the filenaming format when creating a bulk download zip 2022-12-08 18:32:14 -08:00
Trenton H
ff4a8b37bd Install all wheels in the dir instead of specific names 2022-12-08 12:30:20 -08:00
Paperless-ngx Translation Bot [bot]
37d3a624b7 New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 10:32:55 -08:00
Paperless-ngx Translation Bot [bot]
493f6173da New translations django.po (Arabic)
[ci skip]
2022-12-08 09:18:26 -08:00
Paperless-ngx Translation Bot [bot]
272e87b741 New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 09:18:25 -08:00
Paperless-ngx Translation Bot [bot]
2b5e6f7a9d New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 07:24:26 -08:00
Paperless-ngx Translation Bot [bot]
70960f86ba New translations django.po (Arabic)
[ci skip]
2022-12-08 06:25:05 -08:00
Paperless-ngx Translation Bot [bot]
ee4d25567c New translations messages.xlf (Arabic)
[ci skip]
2022-12-08 06:25:04 -08:00
Paperless-ngx Translation Bot [bot]
80a126e838 New translations django.po (Arabic)
[ci skip]
2022-12-08 04:23:05 -08:00
Paperless-ngx Translation Bot [bot]
c02bd66b3f New translations django.po (Arabic)
[ci skip]
2022-12-08 03:15:18 -08:00
Trenton H
cea6720c1a Updates the pre-commit hook versions 2022-12-07 19:12:15 -08:00
Trenton H
700d58058c Updates to qpdf 11.2 and bulk updates the rest of the deps 2022-12-07 19:12:15 -08:00
Paperless-ngx Translation Bot [bot]
33e413af65 New translations django.po (Arabic)
[ci skip]
2022-12-07 09:49:36 -08:00
Trenton H
45a13523d4 Note the previous location and that it can be safely deleted if not in use 2022-12-07 09:36:32 -08:00
Trenton Holmes
95257d5723 Documents the configuration for NLTK related settings 2022-12-07 09:36:32 -08:00
Trenton H
8da3ae2c53 Bakes the NLTK data into the image (~60mb) 2022-12-07 09:36:32 -08:00
Paperless-ngx Translation Bot [bot]
f17b541a5b New translations django.po (Arabic)
[ci skip]
2022-12-07 07:39:37 -08:00
Paperless-ngx Translation Bot [bot]
2b2e518dea New translations django.po (Arabic)
[ci skip]
2022-12-07 06:36:32 -08:00
Trenton Holmes
3f6e3a2750 Fixes args handling 2022-12-06 13:30:01 -08:00
Trenton Holmes
14784d5832 Adds the same backoff/retry logic to the other live tests 2022-12-06 13:30:01 -08:00
shamoon
8cd5e25364 Merge pull request #2137 from paperless-ngx/more-docs-cleanup
Chore: Cleanup of new documentation
2022-12-06 11:23:15 -08:00
Trenton Holmes
7788d93227 Further cleanup of docs, including fixing autoconvert issues and general cleanups 2022-12-06 10:45:18 -08:00
Paperless-ngx Translation Bot [bot]
826503802a New translations messages.xlf (German)
[ci skip]
2022-12-05 21:48:39 -08:00
Paperless-ngx Translation Bot [bot]
6db1e36e14 New translations django.po (Italian)
[ci skip]
2022-12-05 03:26:21 -08:00
Paperless-ngx Translation Bot [bot]
3bc4d7dad7 New translations messages.xlf (Italian)
[ci skip]
2022-12-05 03:26:20 -08:00
shamoon
32d546740b Merge pull request #2118 from alexander-bauer/chart-bump 2022-12-04 19:12:27 -08:00
Alexander Bauer
24da3e5034 Bump Helm Chart version to trigger release 2022-12-05 02:51:35 +00:00
Paperless-ngx Translation Bot [bot]
9e295ddf4f New translations messages.xlf (Serbian (Latin))
[ci skip]
2022-12-04 17:37:25 -08:00
Paperless-ngx Translation Bot [bot]
eff6f2fb01 New translations messages.xlf (French)
[ci skip]
2022-12-04 17:37:23 -08:00
Paperless-ngx Translation Bot [bot]
c597da495c New translations messages.xlf (Spanish)
[ci skip]
2022-12-04 17:37:22 -08:00
Paperless-ngx Translation Bot [bot]
de5e9c95ec New translations messages.xlf (Arabic)
[ci skip]
2022-12-04 17:37:21 -08:00
Paperless-ngx Translation Bot [bot]
4e27242373 New translations messages.xlf (Belarusian)
[ci skip]
2022-12-04 17:37:19 -08:00
Paperless-ngx Translation Bot [bot]
7bf1e24616 New translations messages.xlf (Czech)
[ci skip]
2022-12-04 17:37:18 -08:00
Paperless-ngx Translation Bot [bot]
fd0759bf6f New translations messages.xlf (Danish)
[ci skip]
2022-12-04 17:37:17 -08:00
Paperless-ngx Translation Bot [bot]
d6bbf2cc8d New translations messages.xlf (Finnish)
[ci skip]
2022-12-04 17:37:15 -08:00
Paperless-ngx Translation Bot [bot]
80495d42de New translations messages.xlf (Hebrew)
[ci skip]
2022-12-04 17:37:14 -08:00
Paperless-ngx Translation Bot [bot]
eac21f773f New translations messages.xlf (Italian)
[ci skip]
2022-12-04 17:37:13 -08:00
Paperless-ngx Translation Bot [bot]
52f5831657 New translations messages.xlf (Dutch)
[ci skip]
2022-12-04 17:37:11 -08:00
Paperless-ngx Translation Bot [bot]
f35f33539a New translations messages.xlf (Romanian)
[ci skip]
2022-12-04 17:37:10 -08:00
Paperless-ngx Translation Bot [bot]
46f310603b New translations messages.xlf (Norwegian)
[ci skip]
2022-12-04 17:37:09 -08:00
Paperless-ngx Translation Bot [bot]
531d3f03f9 New translations messages.xlf (Portuguese)
[ci skip]
2022-12-04 17:37:08 -08:00
Paperless-ngx Translation Bot [bot]
85cfd7610d New translations messages.xlf (Russian)
[ci skip]
2022-12-04 17:37:06 -08:00
Paperless-ngx Translation Bot [bot]
201b77189a New translations messages.xlf (Slovenian)
[ci skip]
2022-12-04 17:37:05 -08:00
Paperless-ngx Translation Bot [bot]
5b76b45e33 New translations messages.xlf (Swedish)
[ci skip]
2022-12-04 17:37:04 -08:00
Paperless-ngx Translation Bot [bot]
bf2fac9393 New translations messages.xlf (Turkish)
[ci skip]
2022-12-04 17:37:02 -08:00
Paperless-ngx Translation Bot [bot]
5a3affe8c0 New translations messages.xlf (Chinese Simplified)
[ci skip]
2022-12-04 17:37:01 -08:00
Paperless-ngx Translation Bot [bot]
a5834393b3 New translations messages.xlf (Portuguese, Brazilian)
[ci skip]
2022-12-04 17:36:59 -08:00
Paperless-ngx Translation Bot [bot]
cd6e37c520 New translations messages.xlf (Croatian)
[ci skip]
2022-12-04 17:36:58 -08:00
Paperless-ngx Translation Bot [bot]
af51165229 New translations messages.xlf (Luxembourgish)
[ci skip]
2022-12-04 17:36:57 -08:00
Paperless-ngx Translation Bot [bot]
d480620be9 New translations messages.xlf (Polish)
[ci skip]
2022-12-04 17:36:55 -08:00
Paperless-ngx Translation Bot [bot]
d470de3576 New translations messages.xlf (German)
[ci skip]
2022-12-04 17:36:54 -08:00
Trenton Holmes
538249b26c Adds documentation 2022-12-04 16:53:28 -08:00
shamoon
fb9d3f736b Merge pull request #2000 from paperless-ngx/feature-frontend-paperless-mail
Feature: frontend paperless mail
2022-12-04 16:47:58 -08:00
Trenton Holmes
a6b7beaf6b Adds option to allow a user to export directory to a zipfile 2022-12-04 16:38:25 -08:00
Trenton H
4d4d545343 Merge pull request #848 from p-h-a-i-l/feature-consume-eml
Feature ability to consume mails and eml files
2022-12-04 16:34:41 -08:00
Trenton Holmes
049dc17902 Moves where the mail views live and puts the ordering on those 2022-12-04 16:33:07 -08:00
shamoon
b0ca57a7f0 Merge pull request #2114 from paperless-ngx/v1.10.2-changelog
[Documentation] Add v1.10.2 changelog
2022-12-04 14:36:00 -08:00
Michael Shamoon
cdd49c5142 Update frontend compilation info 2022-12-04 14:32:08 -08:00
Trenton Holmes
4b31e5d0b4 Fixes my broken formatting 2022-12-04 14:00:59 -08:00
Trenton Holmes
8076ebd78c Merge remote-tracking branch 'upstream/dev' into feature-consume-eml 2022-12-04 13:55:46 -08:00
github-actions
c864b3cd19 Changelog v1.10.2 - GHA 2022-12-04 21:17:16 +00:00
Trenton Holmes
2704bcb979 Resets to -dev versioning 2022-12-04 13:06:18 -08:00
Michael Shamoon
ce9f604d81 Explicit default ordering for rule / account views 2022-12-03 09:31:39 -08:00
Michael Shamoon
4f876db5d1 prevent loss of unsaved changes to settings on tab nav 2022-12-03 09:31:39 -08:00
Michael Shamoon
5e5f56dc67 Re-org where some of the new classes are found 2022-12-03 09:31:39 -08:00
Michael Shamoon
93fab8bb95 Apply code suggestions from @stumpylog 2022-12-03 09:31:39 -08:00
Michael Shamoon
35ca2195fe frontend mail rule validation
Display non-field validation errors, hide action param field if not needed
2022-12-03 09:31:39 -08:00
Michael Shamoon
7ace66d7fd fix edit dialog getters 2022-12-03 09:31:39 -08:00
Michael Shamoon
4f9a31244b Add settings routing 2022-12-03 09:31:24 -08:00
Michael Shamoon
14cf4f7095 Update frontend strings 2022-12-03 09:31:23 -08:00
Michael Shamoon
8bd7c27826 Hide order parameter, fix imap port 2022-12-03 09:31:23 -08:00
Michael Shamoon
8c4f486fe9 API mail rule & account tests
and fix use of assign_tags
2022-12-03 09:31:23 -08:00
Michael Shamoon
2849414445 one-way imap password setting via API, ObfuscatedPasswordField 2022-12-03 09:31:23 -08:00
Michael Shamoon
ea1ea0816f Fix mail account / rule delete 2022-12-03 09:31:23 -08:00
Michael Shamoon
52d3a8703c Dynamically load mail rules / accounts settings 2022-12-03 09:31:23 -08:00
Michael Shamoon
4cb4d6adcd update settings tests to not wait on data which is now on-demand 2022-12-03 09:31:23 -08:00
Michael Shamoon
24444237f2 dynamic loading of settings tab contents 2022-12-03 09:31:23 -08:00
Michael Shamoon
40c8629aef Update welcome tour, move admin button 2022-12-03 09:31:23 -08:00
Michael Shamoon
98cdf614a5 Mail form tweaks
Include add button

Include add button
2022-12-03 09:31:23 -08:00
Michael Shamoon
2eb2d99a91 Update frontend fixtures & tests for compatibility 2022-12-03 09:31:23 -08:00
Michael Shamoon
18ad9bcbf2 Working mail rule & account edit 2022-12-03 09:31:23 -08:00
Michael Shamoon
997bff4917 Update deprecated edit-dialog rxjs 2022-12-03 09:31:22 -08:00
Michael Shamoon
78f9a80895 mail account + rule deletion 2022-12-03 09:31:22 -08:00
Michael Shamoon
9231df7a4a Mail rule edit dialog 2022-12-03 09:31:22 -08:00
Michael Shamoon
6f25917c86 Mail account edit dialog 2022-12-03 09:31:22 -08:00
Michael Shamoon
c41d1a78a8 remove unused toastService from edit dialogs and add confirmation 2022-12-03 09:31:22 -08:00
Michael Shamoon
c3331086d5 Basic data retrieval 2022-12-03 09:31:22 -08:00
phail
fe2db4dbf7 adapt compose file for eml parsing 2022-11-30 10:16:39 +01:00
phail
47c88a6bdd Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-11-30 10:10:57 +01:00
Michael Shamoon
2c1333a75f Dont use document data from /documents API call for edit 2022-11-27 21:19:23 -08:00
Michael Shamoon
3c48ce0225 option to truncate content on /documents endpoint 2022-11-27 21:19:23 -08:00
phail
4aa318598f add test comments 2022-11-20 23:26:20 +01:00
phail
00f39d8b58 add test comments 2022-11-20 22:49:42 +01:00
phail
0b1a16908f Include .eml reference in docs 2022-11-20 20:33:07 +01:00
phail
d9796e5003 change order of elements in parsed Texts 2022-11-20 20:24:36 +01:00
phail
3599bb52c0 minor test improvements 2022-11-20 20:12:41 +01:00
phail
af8a6c3764 fix filenames 2022-11-20 19:53:57 +01:00
Trenton Holmes
6d37ebf79e Fixes one more place which used manual size formatting 2022-11-20 09:15:06 -08:00
Trenton Holmes
f6a70b85f4 Use Django templating engine 2022-11-20 09:13:16 -08:00
Trenton Holmes
538a4219bd Fixes missing return 2022-11-20 09:10:44 -08:00
Trenton Holmes
85c41b79be Adds the new packages without updating other dependencies 2022-11-20 08:02:06 -08:00
phail
df101f5e7a split handle_message function 2022-11-20 16:09:46 +01:00
phail
1fa735eb23 use imagehash instead of bitwise hashing 2022-11-20 15:44:43 +01:00
phail
ebe21a0114 eml parsing requires tika 2022-11-20 14:22:30 +01:00
phail
d132eba143 optimize regex 2022-11-20 12:48:03 +01:00
phail
073c3c8fed use html.escape instead of some self build functions 2022-11-20 12:36:49 +01:00
phail
e3c1bde793 remove log mocking, replace pytest raises, use humanfriendly 2022-11-20 12:06:35 +01:00
phail
b68906b14e merge pipfile 2022-11-13 22:49:52 +01:00
phail
681eecc46e Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-11-13 22:43:55 +01:00
phail
1578e8de2d fix live tests 2022-11-13 22:33:26 +01:00
phail
b01cbc9aa0 add conditions to unittests 2022-11-12 15:48:30 +01:00
phail
acd3832417 merge Pipfile.lock 2022-11-03 21:08:15 +01:00
phail
82b2ba3cc2 Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-11-03 21:00:01 +01:00
phail
3de6e0bcf1 put parser into setup
make test using convert optional
Gotenberg live testing
2022-11-03 00:58:36 +01:00
phail
6df73ae940 gotenberg with modified cmd 2022-10-29 23:20:35 +02:00
phail
2204090151 fix string 2022-10-27 23:53:47 +02:00
phail
3c81a7468b replace thumbnail creation with mock 2022-10-27 23:41:29 +02:00
phail
5ef86f9489 Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-10-27 23:12:51 +02:00
phail
90cb0836bb Downgrade pdf validation to text only 2022-10-27 23:11:41 +02:00
phail
ef1d4264b5 improve test coverage a little 2022-10-27 00:27:15 +02:00
phail
e1fa59122d Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-10-26 20:59:49 +02:00
phail
5bf26369e2 remove erroring paramerter 2022-10-25 21:17:40 +02:00
phail
36239ba09f rename help text 2022-10-24 22:15:33 +02:00
phail
318c1d2fbd Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-10-24 21:12:35 +02:00
phail
e7c40fc3dc Update Pipfile 2022-10-23 22:02:11 +02:00
phail
0da0b1c062 update variable names 2022-10-23 21:39:15 +02:00
phail
08988e11f8 Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-10-23 20:37:22 +02:00
phail
30372b0e85 add tests for mail_to_html and generate_pdf_from_mail 2022-10-23 17:18:10 +02:00
phail
567e89d1c7 test for broken eml, add test_generate_pdf 2022-10-22 02:25:23 +02:00
phail
f1f5227ccd add unittest for external images 2022-10-22 00:44:32 +02:00
phail
09b5bd17f2 add unittest for generate_pdf_from_html 2022-10-19 23:19:33 +02:00
phail
e384bd78c5 add unittest for transform_inline_html 2022-10-18 23:48:07 +02:00
phail
fda844f64c add unittest for parse 2022-10-15 15:41:43 +02:00
phail
daf90399bd Add unitest for tika_parse() 2022-10-15 13:13:29 +02:00
phail
3d37e49c1a add 2 more tests 2022-10-14 15:43:43 +02:00
phail
261c6fb990 add unittest for get_thumbnail 2022-10-13 01:03:09 +02:00
phail
cdd2b99b6b Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-07-12 00:09:10 +02:00
phail
d0a0ae91c4 Merge branch 'dev' into feature-consume-eml 2022-05-20 19:29:52 +02:00
phail
c04b9fd7f6 switch to From: Header instead of date to compensate for older libmagic versions 2022-05-19 22:51:17 +02:00
phail
6809b15ce1 workaround for wrong mime detection of .eml files 2022-05-14 16:47:12 +02:00
phail
c317eca1ca add attachment size to pdf 2022-05-05 00:20:32 +02:00
phail
466afa8203 fix consumption of mails without html
split pdf generation functions
2022-05-04 23:42:59 +02:00
phail
c2e3dc76d9 add parsing of inline attachments
remove insecure chromium option
fix html parsing
2022-05-03 23:21:33 +02:00
phail
5a899664f8 remove .eml parser from tika 2022-05-03 18:02:08 +02:00
phail
990e905a04 Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-05-03 17:42:56 +02:00
phail
6b7155a849 merge migrations 2022-04-30 17:33:12 +02:00
phail
47851ddd3f Merge remote-tracking branch 'paperless/dev' into feature-consume-eml 2022-04-29 23:52:56 +02:00
phail
47189643ff add eml parser to paperless_mail 2022-04-29 22:58:11 +02:00
phail
c1efe11cf3 improve pdf generation 2022-04-27 23:32:10 +02:00
phail
0e40ef5f35 add css for pdf generation 2022-04-27 19:52:59 +02:00
phail
c8081595c4 improve pdf generation 2022-04-26 23:25:48 +02:00
phail
a2b5b3b253 moved files 2022-04-26 23:12:36 +02:00
phail
790bcf05ed add prototype archive pdf 2022-04-25 20:55:00 +02:00
phail
d8d2d53c59 fix Mail actions mixup 2022-04-19 20:14:31 +02:00
phail
027897ff03 work in progress Mail parsing 2022-04-19 00:39:00 +02:00
phail
cca576f518 add feature to consume imap mail als .eml 2022-04-15 14:40:02 +02:00
phail
5fcf1b5434 remove uneeded print and fix merge fail 2022-04-14 00:19:30 +02:00
phail
942b5aa9df Merge branch 'dev' into fix-mail-starttls 2022-04-13 23:55:38 +02:00
phail
c05b39a056 fix unittest 2022-04-13 23:37:21 +02:00
phail
3c8196527f adapt to starttls interface change in imap_tools
pin imap-tools version to avoid breaking changes
improve mail log
2022-04-09 13:07:14 +02:00
195 changed files with 45148 additions and 8604 deletions

View File

@@ -1,6 +1,6 @@
{
"qpdf": {
"version": "11.1.1"
"version": "11.2.0"
},
"jbig2enc": {
"version": "0.29",

View File

@@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
Have a question? 👉 [Start a new discussion](https://github.com/paperless-ngx/paperless-ngx/discussions/new) or [ask in chat](https://matrix.to/#/#paperless:adnidor.de).
Have a question? 👉 [Start a new discussion](https://github.com/paperless-ngx/paperless-ngx/discussions/new) or [ask in chat](https://matrix.to/#/#paperlessngx:matrix.org).
Before opening an issue, please double check:

View File

@@ -4,7 +4,7 @@ contact_links:
url: https://github.com/paperless-ngx/paperless-ngx/discussions
about: This issue tracker is not for support questions. Please refer to our Discussions.
- name: 💬 Chat
url: https://matrix.to/#/#paperless:adnidor.de
url: https://matrix.to/#/#paperlessngx:matrix.org
about: Want to discuss Paperless-ngx with others? Check out our chat.
- name: 🚀 Feature Request
url: https://github.com/paperless-ngx/paperless-ngx/discussions/new?category=feature-requests

View File

@@ -4,6 +4,7 @@ autolabeler:
- '/^fix/'
title:
- "/^fix/i"
- "/^Bugfix/i"
- label: "enhancement"
branch:
- '/^feature/'
@@ -13,6 +14,9 @@ categories:
- title: 'Breaking Changes'
labels:
- 'breaking-change'
- title: 'Notable Changes'
labels:
- 'notable'
- title: 'Features'
labels:
- 'enhancement'
@@ -20,7 +24,8 @@ categories:
labels:
- 'bug'
- title: 'Documentation'
label: 'documentation'
labels:
- 'documentation'
- title: 'Maintenance'
labels:
- 'chore'
@@ -29,7 +34,8 @@ categories:
- 'ci-cd'
- title: 'Dependencies'
collapse-after: 3
label: 'dependencies'
labels:
- 'dependencies'
- title: 'All App Changes'
labels:
- 'frontend'
@@ -46,6 +52,8 @@ include-labels:
- 'frontend'
- 'backend'
- 'ci-cd'
- 'breaking-change'
- 'notable'
category-template: '### $TITLE'
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
change-title-escapes: '\<*_&#@'

View File

@@ -16,7 +16,7 @@ on:
jobs:
pre-commit:
name: Linting Checks
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
-
name: Checkout repository
@@ -34,7 +34,7 @@ jobs:
documentation:
name: "Build Documentation"
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs:
- pre-commit
steps:
@@ -44,7 +44,7 @@ jobs:
-
name: Install pipenv
run: |
pipx install pipenv==2022.10.12
pipx install pipenv==2022.11.30
-
name: Set up Python
uses: actions/setup-python@v4
@@ -73,7 +73,7 @@ jobs:
documentation-deploy:
name: "Deploy Documentation"
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs:
- documentation
@@ -92,7 +92,7 @@ jobs:
tests-backend:
name: "Tests (${{ matrix.python-version }})"
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs:
- pre-commit
strategy:
@@ -106,6 +106,10 @@ jobs:
PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }}
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
# Skip Tests which require convert
PAPERLESS_TEST_SKIP_CONVERT: 1
# Enable Gotenberg end to end testing
GOTENBERG_LIVE: 1
steps:
-
name: Checkout
@@ -120,7 +124,7 @@ jobs:
-
name: Install pipenv
run: |
pipx install pipenv==2022.10.12
pipx install pipenv==2022.11.30
-
name: Set up Python
uses: actions/setup-python@v4
@@ -177,7 +181,7 @@ jobs:
tests-frontend:
name: "Tests Frontend"
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs:
- pre-commit
strategy:
@@ -191,13 +195,14 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: cd src-ui && npm ci
- run: cd src-ui && npm run lint
- run: cd src-ui && npm run test
- run: cd src-ui && npm run e2e:ci
prepare-docker-build:
name: Prepare Docker Pipeline Data
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
# If the push triggered the installer library workflow, wait for it to
# complete here. This ensures the required versions for the final
# image have been built, while not waiting at all if the versions haven't changed
@@ -274,7 +279,7 @@ jobs:
# build and push image to docker hub.
build-docker-image:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
concurrency:
group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }}
cancel-in-progress: true
@@ -379,7 +384,7 @@ jobs:
build-release:
needs:
- build-docker-image
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
-
name: Checkout
@@ -458,7 +463,7 @@ jobs:
path: dist/paperless-ngx.tar.xz
publish-release:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
outputs:
prerelease: ${{ steps.get_version.outputs.prerelease }}
changelog: ${{ steps.create-release.outputs.body }}
@@ -507,7 +512,7 @@ jobs:
asset_content_type: application/x-xz
append-changelog:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs:
- publish-release
if: needs.publish-release.outputs.prerelease == 'false'

View File

@@ -1,17 +1,14 @@
# This workflow runs on certain conditions to check for and potentially
# delete container images from the GHCR which no longer have an associated
# code branch.
# Requires a PAT with the correct scope set in the secrets
# Requires a PAT with the correct scope set in the secrets.
#
# This workflow will not trigger runs on forked repos.
name: Cleanup Image Tags
on:
schedule:
- cron: '0 0 * * SAT'
delete:
pull_request:
types:
- closed
push:
paths:
- ".github/workflows/cleanup-tags.yml"
@@ -26,7 +23,8 @@ concurrency:
jobs:
cleanup-images:
name: Cleanup Image Tags for ${{ matrix.primary-name }}
runs-on: ubuntu-latest
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-22.04
strategy:
matrix:
include:

View File

@@ -23,7 +23,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
actions: read
contents: read

View File

@@ -34,7 +34,7 @@ concurrency:
jobs:
prepare-docker-build:
name: Prepare Docker Image Version Data
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
-
name: Set ghcr repository name
@@ -127,6 +127,7 @@ jobs:
uses: ./.github/workflows/reusable-workflow-builder.yml
with:
dockerfile: ./docker-builders/Dockerfile.qpdf
build-platforms: linux/amd64
build-json: ${{ needs.prepare-docker-build.outputs.qpdf-json }}
build-args: |
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}

View File

@@ -24,7 +24,7 @@ env:
jobs:
issue_opened_or_reopened:
name: issue_opened_or_reopened
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
steps:
- name: Add issue to project and set status to ${{ env.todo }}
@@ -37,7 +37,7 @@ jobs:
status_value: ${{ env.todo }} # Target status
pr_opened_or_reopened:
name: pr_opened_or_reopened
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
# write permission is required for autolabeler
pull-requests: write

View File

@@ -9,7 +9,7 @@ on:
jobs:
release_chart:
name: "Release Chart"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3

View File

@@ -13,6 +13,10 @@ on:
required: false
default: ""
type: string
build-platforms:
required: false
default: linux/amd64,linux/arm64,linux/arm/v7
type: string
concurrency:
group: ${{ github.workflow }}-${{ fromJSON(inputs.build-json).name }}-${{ fromJSON(inputs.build-json).version }}
@@ -21,7 +25,7 @@ concurrency:
jobs:
build-image:
name: Build ${{ fromJSON(inputs.build-json).name }} @ ${{ fromJSON(inputs.build-json).version }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
-
name: Checkout
@@ -46,7 +50,7 @@ jobs:
context: .
file: ${{ inputs.dockerfile }}
tags: ${{ fromJSON(inputs.build-json).image_tag }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: ${{ inputs.build-platforms }}
build-args: ${{ inputs.build-args }}
push: true
cache-from: type=registry,ref=${{ fromJSON(inputs.build-json).cache_tag }}

View File

@@ -5,7 +5,7 @@
repos:
# General hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-docstring-first
- id: check-json
@@ -48,23 +48,23 @@ repos:
- id: yesqa
exclude: "(migrations)"
- repo: https://github.com/asottile/add-trailing-comma
rev: "v2.3.0"
rev: "v2.4.0"
hooks:
- id: add-trailing-comma
exclude: "(migrations)"
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8
files: ^src/
args:
- "--config=./src/setup.cfg"
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/asottile/pyupgrade
rev: v3.2.2
rev: v3.3.1
hooks:
- id: pyupgrade
exclude: "(migrations)"
@@ -83,6 +83,6 @@ repos:
args:
- "--tab"
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: "v0.8.0.4"
rev: "v0.9.0.2"
hooks:
- id: shellcheck

View File

@@ -45,7 +45,7 @@ COPY Pipfile* ./
RUN set -eux \
&& echo "Installing pipenv" \
&& python3 -m pip install --no-cache-dir --upgrade pipenv \
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2022.11.30 \
&& echo "Generating requirement.txt" \
&& pipenv requirements > requirements.txt
@@ -58,6 +58,12 @@ LABEL org.opencontainers.image.url="https://github.com/paperless-ngx/paperless-n
LABEL org.opencontainers.image.licenses="GPL-3.0-only"
ARG DEBIAN_FRONTEND=noninteractive
# Buildx provided
ARG TARGETARCH
ARG TARGETVARIANT
# Workflow provided
ARG QPDF_VERSION
#
# Begin installation and configuration
@@ -194,14 +200,10 @@ RUN --mount=type=bind,from=qpdf-builder,target=/qpdf \
--mount=type=bind,from=pikepdf-builder,target=/pikepdf \
set -eux \
&& echo "Installing qpdf" \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf29_*.deb \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/qpdf_*.deb \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/libqpdf29_*.deb \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/qpdf_*.deb \
&& echo "Installing pikepdf and dependencies" \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pyparsing*.whl \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/packaging*.whl \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/lxml*.whl \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/Pillow*.whl \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pikepdf*.whl \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/*.whl \
&& python3 -m pip list \
&& echo "Installing psycopg2" \
&& python3 -m pip install --no-cache-dir /psycopg2/usr/src/wheels/psycopg2*.whl \
@@ -228,6 +230,10 @@ RUN set -eux \
&& python3 -m pip install --no-cache-dir --upgrade wheel \
&& echo "Installing Python requirements" \
&& python3 -m pip install --default-timeout=1000 --no-cache-dir --requirement requirements.txt \
&& echo "Installing NLTK data" \
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/local/share/nltk_data" snowball_data \
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/local/share/nltk_data" stopwords \
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/local/share/nltk_data" punkt \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \

21
Pipfile
View File

@@ -30,8 +30,6 @@ psycopg2 = "*"
rapidfuzz = "*"
redis = {extras = ["hiredis"], version = "*"}
scikit-learn = "~=1.1"
# Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/)
scipy = "==1.8.1"
numpy = "*"
whitenoise = "~=6.2"
watchdog = "~=2.1"
@@ -43,9 +41,6 @@ tika = "*"
# TODO: This will sadly also install daphne+dependencies,
# which an ASGI server we don't need. Adds about 15MB image size.
channels = "~=3.0"
# Locked version until https://github.com/django/channels_redis/issues/332
# is resolved
channels-redis = "==3.4.1"
uvicorn = {extras = ["standard"], version = "*"}
concurrent-log-handler = "*"
"pdfminer.six" = "*"
@@ -60,6 +55,21 @@ setproctitle = "*"
nltk = "*"
pdf2image = "*"
flower = "*"
bleach = "*"
#
# Packages locked due to issues (try to check if these are fixed in a release every so often)
#
# Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/)
scipy = "==1.8.1"
# Newer versions aren't builting yet (see https://www.piwheels.org/project/cryptography/)
cryptography = "==38.0.1"
# Locked version until https://github.com/django/channels_redis/issues/332
# is resolved
channels-redis = "==3.4.1"
[dev-packages]
coveralls = "*"
@@ -76,4 +86,5 @@ black = "*"
pre-commit = "*"
sphinx-autobuild = "*"
myst-parser = "*"
imagehash = "*"
mkdocs-material = "*"

1508
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,9 +10,9 @@
# Example Usage:
# ./build-docker-image.sh Dockerfile -t paperless-ngx:my-awesome-feature
set -eux
set -eu
if ! command -v jq; then
if ! command -v jq &> /dev/null ; then
echo "jq required"
exit 1
elif [ ! -f "$1" ]; then
@@ -20,28 +20,62 @@ elif [ ! -f "$1" ]; then
exit 1
fi
# Parse what we can from Pipfile.lock
pikepdf_version=$(jq ".default.pikepdf.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
psycopg2_version=$(jq ".default.psycopg2.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
# Read this from the other config file
qpdf_version=$(jq ".qpdf.version" .build-config.json | sed 's/"//g')
jbig2enc_version=$(jq ".jbig2enc.version" .build-config.json | sed 's/"//g')
# Get the branch name (used for caching)
branch_name=$(git rev-parse --abbrev-ref HEAD)
# https://docs.docker.com/develop/develop-images/build_enhancements/
# Required to use cache-from
export DOCKER_BUILDKIT=1
# Parse eithe Pipfile.lock or the .build-config.json
jbig2enc_version=$(jq ".jbig2enc.version" .build-config.json | sed 's/"//g')
qpdf_version=$(jq ".qpdf.version" .build-config.json | sed 's/"//g')
psycopg2_version=$(jq ".default.psycopg2.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
pikepdf_version=$(jq ".default.pikepdf.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
docker build --file "$1" \
base_filename="$(basename -- "${1}")"
build_args_str=""
cache_from_str=""
case "${base_filename}" in
*.jbig2enc)
build_args_str="--build-arg JBIG2ENC_VERSION=${jbig2enc_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/jbig2enc:${jbig2enc_version}"
;;
*.psycopg2)
build_args_str="--build-arg PSYCOPG2_VERSION=${psycopg2_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/psycopg2:${psycopg2_version}"
;;
*.qpdf)
build_args_str="--build-arg QPDF_VERSION=${qpdf_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/qpdf:${qpdf_version}"
;;
*.pikepdf)
build_args_str="--build-arg QPDF_VERSION=${qpdf_version} --build-arg PIKEPDF_VERSION=${pikepdf_version} --build-arg PILLOW_VERSION=${pillow_version} --build-arg LXML_VERSION=${lxml_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/pikepdf:${pikepdf_version}"
;;
Dockerfile)
build_args_str="--build-arg QPDF_VERSION=${qpdf_version} --build-arg PIKEPDF_VERSION=${pikepdf_version} --build-arg PSYCOPG2_VERSION=${psycopg2_version} --build-arg JBIG2ENC_VERSION=${jbig2enc_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:${branch_name} --cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:dev"
;;
*)
echo "Unable to match ${base_filename}"
exit 1
;;
esac
read -r -a build_args_arr <<< "${build_args_str}"
read -r -a cache_from_arr <<< "${cache_from_str}"
set -eux
docker buildx build --file "${1}" \
--progress=plain \
--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:"${branch_name}" \
--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:dev \
--build-arg JBIG2ENC_VERSION="${jbig2enc_version}" \
--build-arg QPDF_VERSION="${qpdf_version}" \
--build-arg PIKEPDF_VERSION="${pikepdf_version}" \
--build-arg PILLOW_VERSION="${pillow_version}" \
--build-arg LXML_VERSION="${lxml_version}" \
--build-arg PSYCOPG2_VERSION="${psycopg2_version}" "${@:2}" .
--output=type=docker \
"${cache_from_arr[@]}" \
"${build_args_arr[@]}" \
"${@:2}" .

View File

@@ -3,7 +3,7 @@ apiVersion: v2
appVersion: "1.9.2"
description: Paperless-ngx - Index and archive all of your scanned paper documents
name: paperless
version: 10.0.0
version: 10.0.1
kubeVersion: ">=1.16.0-0"
keywords:
- paperless

View File

@@ -16,7 +16,13 @@ FROM python:3.9-slim-bullseye as main
LABEL org.opencontainers.image.description="A intermediate image with pikepdf wheel built"
# Buildx provided
ARG TARGETARCH
ARG TARGETVARIANT
ARG DEBIAN_FRONTEND=noninteractive
# Workflow provided
ARG QPDF_VERSION
ARG PIKEPDF_VERSION
# These are not used, but will still bust the cache if one changes
# Otherwise, the main image will try to build thing (and fail)
@@ -54,7 +60,7 @@ ARG BUILD_PACKAGES="\
WORKDIR /usr/src
COPY --from=qpdf-builder /usr/src/qpdf/*.deb ./
COPY --from=qpdf-builder /usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/*.deb ./
# As this is an base image for a multi-stage final image
# the added size of the install is basically irrelevant
@@ -77,6 +83,8 @@ RUN set -eux \
&& python3 -m pip wheel \
# Build the package at the required version
pikepdf==${PIKEPDF_VERSION} \
# Look to piwheels for additional pre-built wheels
--extra-index-url https://www.piwheels.org/simple \
# Output the *.whl into this directory
--wheel-dir wheels \
# Do not use a binary packge for the package being built
@@ -86,6 +94,8 @@ RUN set -eux \
# Don't cache build files
--no-cache-dir \
&& ls -ahl wheels \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./wheels/pkg-list.txt \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \

View File

@@ -42,6 +42,8 @@ RUN set -eux \
# Don't cache build files
--no-cache-dir \
&& ls -ahl wheels/ \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./wheels/pkg-list.txt \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \

View File

@@ -1,48 +1,156 @@
# This Dockerfile compiles the jbig2enc library
# Inputs:
# - QPDF_VERSION - the version of qpdf to build a .deb.
# Must be present as a deb-src in bookworm
#
# Stage: pre-build
# Purpose:
# - Installs common packages
# - Sets common environment variables related to dpkg
# - Aquires the qpdf source from bookwork
# Useful Links:
# - https://qpdf.readthedocs.io/en/stable/installation.html#system-requirements
# - https://wiki.debian.org/Multiarch/HOWTO
# - https://wiki.debian.org/CrossCompiling
#
FROM debian:bullseye-slim as main
FROM debian:bullseye-slim as pre-build
LABEL org.opencontainers.image.description="A intermediate image with qpdf built"
ARG DEBIAN_FRONTEND=noninteractive
# This must match to pikepdf's minimum at least
ARG QPDF_VERSION
ARG BUILD_PACKAGES="\
build-essential \
debhelper \
ARG COMMON_BUILD_PACKAGES="\
cmake \
debhelper\
debian-keyring \
devscripts \
equivs \
libtool \
# https://qpdf.readthedocs.io/en/stable/installation.html#system-requirements
libjpeg62-turbo-dev \
libgnutls28-dev \
dpkg-dev \
equivs \
packaging-dev \
cmake \
zlib1g-dev"
libtool"
ENV DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2"
WORKDIR /usr/src
RUN set -eux \
&& echo "Installing build tools" \
&& echo "Installing common packages" \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends $BUILD_PACKAGES \
&& echo "Getting qpdf src" \
&& apt-get install --yes --quiet --no-install-recommends ${COMMON_BUILD_PACKAGES} \
&& echo "Getting qpdf source" \
&& echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \
&& apt-get update \
&& mkdir qpdf \
&& cd qpdf \
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm \
&& echo "Building qpdf" \
&& cd qpdf-$QPDF_VERSION \
&& export DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
&& ls -ahl ../*.deb \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \
&& rm -rf /var/lib/apt/lists/*
&& apt-get update --quiet \
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm
#
# Stage: amd64-builder
# Purpose: Builds qpdf for x86_64 (native build)
#
FROM pre-build as amd64-builder
ARG AMD64_BUILD_PACKAGES="\
build-essential \
libjpeg62-turbo-dev:amd64 \
libgnutls28-dev:amd64 \
zlib1g-dev:amd64"
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
RUN set -eux \
&& echo "Beginning amd64" \
&& echo "Install amd64 packages" \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends ${AMD64_BUILD_PACKAGES} \
&& echo "Building amd64" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
&& echo "Removing debug files" \
&& rm -f ../libqpdf29-dbgsym* \
&& rm -f ../qpdf-dbgsym* \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
#
# Stage: armhf-builder
# Purpose:
# - Sets armhf specific environment
# - Builds qpdf for armhf (cross compile)
#
FROM pre-build as armhf-builder
ARG ARMHF_PACKAGES="\
crossbuild-essential-armhf \
libjpeg62-turbo-dev:armhf \
libgnutls28-dev:armhf \
zlib1g-dev:armhf"
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
ENV CXX="/usr/bin/arm-linux-gnueabihf-g++" \
CC="/usr/bin/arm-linux-gnueabihf-gcc"
RUN set -eux \
&& echo "Beginning armhf" \
&& echo "Install armhf packages" \
&& dpkg --add-architecture armhf \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends ${ARMHF_PACKAGES} \
&& echo "Building armhf" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean --host-arch armhf \
&& echo "Removing debug files" \
&& rm -f ../libqpdf29-dbgsym* \
&& rm -f ../qpdf-dbgsym* \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
#
# Stage: aarch64-builder
# Purpose:
# - Sets aarch64 specific environment
# - Builds qpdf for aarch64 (cross compile)
#
FROM pre-build as aarch64-builder
ARG ARM64_PACKAGES="\
crossbuild-essential-arm64 \
libjpeg62-turbo-dev:arm64 \
libgnutls28-dev:arm64 \
zlib1g-dev:arm64"
ENV CXX="/usr/bin/aarch64-linux-gnu-g++" \
CC="/usr/bin/aarch64-linux-gnu-gcc"
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
RUN set -eux \
&& echo "Beginning arm64" \
&& echo "Install arm64 packages" \
&& dpkg --add-architecture arm64 \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends ${ARM64_PACKAGES} \
&& echo "Building arm64" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean --host-arch arm64 \
&& echo "Removing debug files" \
&& rm -f ../libqpdf29-dbgsym* \
&& rm -f ../qpdf-dbgsym* \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
#
# Stage: package
# Purpose: Holds the compiled .deb files in arch/variant specific folders
#
FROM alpine:3.17 as package
LABEL org.opencontainers.image.description="A image with qpdf installers stored in architecture & version specific folders"
ARG QPDF_VERSION
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/amd64
COPY --from=amd64-builder /usr/src/*.deb ./
COPY --from=amd64-builder /usr/src/pkg-list.txt ./
# Note this is ${TARGETARCH}${TARGETVARIANT} for armv7
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/armv7
COPY --from=armhf-builder /usr/src/*.deb ./
COPY --from=armhf-builder /usr/src/pkg-list.txt ./
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/arm64
COPY --from=aarch64-builder /usr/src/*.deb ./
COPY --from=aarch64-builder /usr/src/pkg-list.txt ./

View File

@@ -11,9 +11,12 @@ services:
container_name: gotenberg
network_mode: host
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
command:
- "gotenberg"
- "--chromium-disable-routes=true"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: ghcr.io/paperless-ngx/tika:latest
hostname: tika

View File

@@ -49,8 +49,6 @@ services:
MARIADB_USER: paperless
MARIADB_PASSWORD: paperless
MARIADB_ROOT_PASSWORD: paperless
ports:
- "3306:3306"
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
@@ -87,9 +85,12 @@ services:
gotenberg:
image: docker.io/gotenberg/gotenberg:7.6
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
command:
- "gotenberg"
- "--chromium-disable-routes=true"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: ghcr.io/paperless-ngx/tika:latest

View File

@@ -45,8 +45,6 @@ services:
MARIADB_USER: paperless
MARIADB_PASSWORD: paperless
MARIADB_ROOT_PASSWORD: paperless
ports:
- "3306:3306"
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest

View File

@@ -79,9 +79,13 @@ services:
gotenberg:
image: docker.io/gotenberg/gotenberg:7.6
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
command:
- "gotenberg"
- "--chromium-disable-routes=true"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: ghcr.io/paperless-ngx/tika:latest

View File

@@ -67,9 +67,13 @@ services:
gotenberg:
image: docker.io/gotenberg/gotenberg:7.6
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
command:
- "gotenberg"
- "--chromium-disable-routes=true"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: ghcr.io/paperless-ngx/tika:latest

View File

@@ -53,30 +53,6 @@ map_folders() {
export CONSUME_DIR="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}"
}
nltk_data () {
# Store the NLTK data outside the Docker container
local -r nltk_data_dir="${DATA_DIR}/nltk"
local -r truthy_things=("yes y 1 t true")
# If not set, or it looks truthy
if [[ -z "${PAPERLESS_ENABLE_NLTK}" ]] || [[ "${truthy_things[*]}" =~ ${PAPERLESS_ENABLE_NLTK,} ]]; then
# Download or update the snowball stemmer data
python3 -W ignore::RuntimeWarning -m nltk.downloader -d "${nltk_data_dir}" snowball_data
# Download or update the stopwords corpus
python3 -W ignore::RuntimeWarning -m nltk.downloader -d "${nltk_data_dir}" stopwords
# Download or update the punkt tokenizer data
python3 -W ignore::RuntimeWarning -m nltk.downloader -d "${nltk_data_dir}" punkt
else
echo "Skipping NLTK data download"
fi
}
custom_container_init() {
# Mostly borrowed from the LinuxServer.io base image
# https://github.com/linuxserver/docker-baseimage-ubuntu/tree/bionic/root/etc/cont-init.d
@@ -157,8 +133,6 @@ initialize() {
echo "Creating directory ${tmp_dir}"
mkdir -p "${tmp_dir}"
nltk_data
set +e
echo "Adjusting permissions of paperless files. This may take a while."
chown -R paperless:paperless ${tmp_dir}
@@ -191,10 +165,6 @@ install_languages() {
for lang in "${langs[@]}"; do
pkg="tesseract-ocr-$lang"
# English is installed by default
#if [[ "$lang" == "eng" ]]; then
# continue
#fi
if dpkg -s "$pkg" &>/dev/null; then
echo "Package $pkg already installed!"

View File

@@ -20,7 +20,6 @@ wait_for_postgres() {
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(("$attempt_num" + 1))
@@ -37,6 +36,8 @@ wait_for_mariadb() {
local attempt_num=1
local -r max_attempts=5
# Disable warning, host and port can't have spaces
# shellcheck disable=SC2086
while ! true > /dev/tcp/$host/$port; do
if [ $attempt_num -eq $max_attempts ]; then
@@ -67,10 +68,16 @@ migrations() {
# of the current container starts.
flock 200
echo "Apply database migrations..."
python3 manage.py migrate
python3 manage.py migrate --skip-checks --no-input
) 200>"${DATA_DIR}/migration_lock"
}
django_checks() {
# Explicitly run the Django system checks
echo "Running Django checks"
python3 manage.py check
}
search_index() {
local -r index_version=1
@@ -100,6 +107,8 @@ do_work() {
migrations
django_checks
search_index
superuser

View File

@@ -9,7 +9,7 @@ Before making backups, make sure that paperless is not running.
Options available to any installation of paperless:
- Use the [document exporter](administration#exporter). The document exporter exports all your documents,
- Use the [document exporter](#exporter). The document exporter exports all your documents,
thumbnails and metadata to a specific folder. You may import your
documents into a fresh instance of paperless again or store your
documents in another DMS with this export.
@@ -52,7 +52,7 @@ Options available to bare-metal and non-docker installations:
## Updating Paperless {#updating}
### Docker Route
### Docker Route {#docker-updating}
If a new release of paperless-ngx is available, upgrading depends on how
you installed paperless-ngx in the first place. The releases are
@@ -68,23 +68,23 @@ $ docker-compose down
After that, [make a backup](#backup).
A. If you pull the image from the docker hub, all you need to do is:
1. If you pull the image from the docker hub, all you need to do is:
``` shell-session
$ docker-compose pull
$ docker-compose up
```
```shell-session
$ docker-compose pull
$ docker-compose up
```
The docker-compose files refer to the `latest` version, which is
always the latest stable release.
The docker-compose files refer to the `latest` version, which is
always the latest stable release.
B. If you built the image yourself, do the following:
2. If you built the image yourself, do the following:
``` shell-session
$ git pull
$ docker-compose build
$ docker-compose up
```
```shell-session
$ git pull
$ docker-compose build
$ docker-compose up
```
Running `docker-compose up` will also apply any new database migrations.
If you see everything working, press CTRL+C once to gracefully stop
@@ -131,7 +131,7 @@ the background.
image: ghcr.io/paperless-ngx/paperless-ngx:1.7
```
### Bare Metal Route
### Bare Metal Route {#bare-metal-updating}
After grabbing the new release and unpacking the contents, do the
following:
@@ -158,7 +158,7 @@ following:
This might not actually do anything. Not every new paperless version
comes with new database migrations.
## Downgrading Paperless
## Downgrading Paperless {#downgrade-paperless}
Downgrades are possible. However, some updates also contain database
migrations (these change the layout of the database and may move data).
@@ -233,6 +233,7 @@ optional arguments:
-c, --compare-checksums
-f, --use-filename-format
-d, --delete
-z --zip
```
`target` is a folder to which the data gets written. This includes
@@ -258,6 +259,9 @@ current export such as files from deleted documents, specify `--delete`.
Be careful when pointing paperless to a directory that already contains
other files.
If `-z` or `--zip` is provided, the export will be a zipfile
in the target directory, named according to the current date.
The filenames generated by this command follow the format
`[date created] [correspondent] [title].[extension]`. If you want
paperless to use `PAPERLESS_FILENAME_FORMAT` for exported filenames
@@ -266,7 +270,7 @@ instead, specify `--use-filename-format`.
### Document importer {#importer}
The document importer takes the export produced by the [Document
exporter](#document-exporter) and imports it into paperless.
exporter](#exporter) and imports it into paperless.
The importer works just like the exporter. You point it at a directory,
and the script does the rest of the work:
@@ -366,7 +370,7 @@ task scheduler.
### Managing filenames {#renamer}
If you use paperless' feature to
[assign custom filenames to your documents](/advanced_usage#file_name_handling), you can use this command to move all your files after
[assign custom filenames to your documents](/advanced_usage#file-name-handling), you can use this command to move all your files after
changing the naming scheme.
!!! warning
@@ -430,9 +434,7 @@ rules.
As of October 2022 Microsoft no longer supports IMAP authentication
for Exchange servers, thus Exchange is no longer supported until a
solution is implemented in the Python IMAP library used by Paperless.
See
[learn.microsoft.com](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online)
See [learn.microsoft.com](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online)
### Creating archived documents {#archiver}
@@ -467,13 +469,13 @@ document.
documents, such as encrypted PDF documents. The archiver will skip over
these documents each time it sees them.
### Managing encryption {#encyption}
### Managing encryption {#encryption}
Documents can be stored in Paperless using GnuPG encryption.
!!! warning
Encryption is deprecated since paperless-ngx 0.9 and doesn't really
Encryption is deprecated since [paperless-ng 0.9](/changelog#paperless-ng-090) and doesn't really
provide any additional security, since you have to store the passphrase
in a configuration file on the same system as the encrypted documents
for paperless to work. Furthermore, the entire text content of the

View File

@@ -50,7 +50,7 @@ and run another document through the consumer. Once complete, you should
see the newly-created document, automatically tagged with the
appropriate data.
### Automatic matching {#automatic_matching}
### Automatic matching {#automatic-matching}
Paperless-ngx comes with a new matching algorithm called _Auto_. This
matching algorithm tries to assign tags, correspondents, document types,
@@ -59,8 +59,8 @@ assigned these on existing documents. It uses a neural network under the
hood.
If, for example, all your bank statements of your account 123 at the
Bank of America are tagged with the tag "bofa*123" and the matching
algorithm of this tag is set to \_Auto*, this neural network will examine
Bank of America are tagged with the tag "bofa123" and the matching
algorithm of this tag is set to _Auto_, this neural network will examine
your documents and automatically learn when to assign this tag.
Paperless tries to hide much of the involved complexity with this
@@ -95,7 +95,7 @@ when using this feature:
of these correspondents to ANY new document, if both are set to
automatic matching.
## Hooking into the consumption process
## Hooking into the consumption process {#consume-hooks}
Sometimes you may want to do something arbitrary whenever a document is
consumed. Rather than try to predict what you may want to do, Paperless
@@ -115,7 +115,7 @@ and then put the path to that script in `paperless.conf` or
asynchronously, you'll have to fork the process in your script and
exit.
### Pre-consumption script
### Pre-consumption script {#pre-consume-script}
Executed after the consumer sees a new document in the consumption
folder, but before any processing of the document is performed. This
@@ -151,7 +151,7 @@ with the newly modified file.
The script's stdout and stderr will be logged line by line to the
webserver log, along with the exit code of the script.
### Post-consumption script {#post_consume_script}
### Post-consumption script {#post-consume-script}
Executed after the consumer has successfully processed a document and
has moved it into paperless. It receives the following environment
@@ -181,33 +181,34 @@ The post consumption script cannot cancel the consumption process.
The script's stdout and stderr will be logged line by line to the
webserver log, along with the exit code of the script.
#### Docker
### Docker {#docker-consume-hooks}
Assumed you have
`/home/foo/paperless-ngx/scripts/post-consumption-example.sh`.
To hook into the consumption process when using Docker, you
will need to pass the scripts into the container via a host mount
in your `docker-compose.yml`.
You can pass that script into the consumer container via a host mount in
your `docker-compose.yml`.
Assuming you have
`/home/paperless-ngx/scripts/post-consumption-example.sh` as a
script which you'd like to run.
```bash
You can pass that script into the consumer container via a host mount:
```yaml
...
consumer:
webserver:
...
volumes:
...
- /home/paperless-ngx/scripts:/path/in/container/scripts/
- /home/paperless-ngx/scripts:/path/in/container/scripts/ # (1)!
environment: # (3)!
...
PAPERLESS_POST_CONSUME_SCRIPT: /path/in/container/scripts/post-consumption-example.sh # (2)!
...
```
Example (docker-compose.yml):
`- /home/foo/paperless-ngx/scripts:/usr/src/paperless/scripts`
which in turn requires the variable `PAPERLESS_POST_CONSUME_SCRIPT` in
`docker-compose.env` to point to
`/path/in/container/scripts/post-consumption-example.sh`.
Example (docker-compose.env):
`PAPERLESS_POST_CONSUME_SCRIPT=/usr/src/paperless/scripts/post-consumption-example.sh`
1. The external scripts directory is mounted to a location inside the container.
2. The internal location of the script is used to set the script to run
3. This can also be set in `docker-compose.env`
Troubleshooting:
@@ -218,7 +219,7 @@ Troubleshooting:
- Pipe your scripts's output to a log file e.g.
`echo "${DOCUMENT_ID}" | tee --append /usr/src/paperless/scripts/post-consumption-example.log`
## File name handling {#file_name_handling}
## File name handling {#file-name-handling}
By default, paperless stores your documents in the media directory and
renames them using the identifier which it has assigned to each
@@ -301,7 +302,7 @@ value.
!!! tip
You can affect how empty placeholders are treated by changing the
following setting to [true]{.title-ref}.
following setting to `true`.
```
PAPERLESS_FILENAME_FORMAT_REMOVE_NONE=True
@@ -316,7 +317,7 @@ value.
Paperless checks the filename of a document whenever it is saved.
Therefore, you need to update the filenames of your documents and move
them after altering this setting by invoking the
[`document renamer <utilities-renamer>`]().
[`document renamer`](/administration#renamer).
!!! warning
@@ -344,7 +345,7 @@ When as single storage layout is not sufficient for your use case,
storage paths come to the rescue. Storage paths allow you to configure
more precisely where each document is stored in the file system.
- Each storage path is a [PAPERLESS_FILENAME_FORMAT]{.title-ref} and
- Each storage path is a `PAPERLESS_FILENAME_FORMAT` and
follows the rules described above
- Each document is assigned a storage path using the matching
algorithms described above, but can be overwritten at any time
@@ -352,7 +353,7 @@ more precisely where each document is stored in the file system.
For example, you could define the following two storage paths:
1. Normal communications are put into a folder structure sorted by
[year/correspondent]{.title-ref}
`year/correspondent`
2. Communications with insurance companies are stored in a flat
structure with longer file names, but containing the full date of
the correspondence.
@@ -363,7 +364,7 @@ Insurances = Insurances/{correspondent}/{created_year}-{created_month}-{created_
```
If you then map these storage paths to the documents, you might get the
following result. For simplicity, [By Year]{.title-ref} defines the same
following result. For simplicity, `By Year` defines the same
structure as in the previous example above.
```text
@@ -384,7 +385,7 @@ structure as in the previous example above.
!!! tip
Defining a storage path is optional. If no storage path is defined for a
document, the global [PAPERLESS_FILENAME_FORMAT]{.title-ref} is applied.
document, the global `PAPERLESS_FILENAME_FORMAT` is applied.
!!! warning
@@ -403,33 +404,38 @@ queued and completed tasks, timing and more. Flower can also be used
with Prometheus, as it exports metrics. For details on its capabilities,
refer to the Flower documentation.
To configure Flower further, create a [flowerconfig.py]{.title-ref} and
place it into the [src/paperless]{.title-ref} directory. For a Docker
To configure Flower further, create a `flowerconfig.py` and
place it into the `src/paperless` directory. For a Docker
installation, you can use volumes to accomplish this:
```yaml
services:
# ...
webserver:
ports:
- 5555:5555 # (2)!
# ...
volumes:
- /path/to/my/flowerconfig.py:/usr/src/paperless/src/paperless/flowerconfig.py:ro
- /path/to/my/flowerconfig.py:/usr/src/paperless/src/paperless/flowerconfig.py:ro # (1)!
```
1. Note the `:ro` tag means the file will be mounted as read only.
2. `flower` runs by default on port 5555, but this can be configured
## Custom Container Initialization
The Docker image includes the ability to run custom user scripts during
startup. This could be utilized for installing additional tools or
Python packages, for example.
Python packages, for example. Scripts are expected to be shell scripts.
To utilize this, mount a folder containing your scripts to the custom
initialization directory, [/custom-cont-init.d]{.title-ref} and place
initialization directory, `/custom-cont-init.d` and place
scripts you wish to run inside. For security, the folder must be owned
by `root` and should have permissions of `a=rx`. Additionally, scripts
must only be writable by `root`.
Your scripts will be run directly before the webserver completes
startup. Scripts will be run by the [root]{.title-ref} user.
startup. Scripts will be run by the `root` user.
If you would like to switch users, the utility `gosu` is available and
preferred over `sudo`.
@@ -445,9 +451,11 @@ services:
webserver:
# ...
volumes:
- /path/to/my/scripts:/custom-cont-init.d:ro
- /path/to/my/scripts:/custom-cont-init.d:ro # (1)!
```
1. Note the `:ro` tag means the folder will be mounted as read only. This is for extra security against changes
## MySQL Caveats {#mysql-caveats}
### Case Sensitivity

View File

@@ -1,7 +1,7 @@
# The REST API
Paperless makes use of the [Django REST
Framework](http://django-rest-framework.org/) standard API interface. It
Framework](https://django-rest-framework.org/) standard API interface. It
provides a browsable API for most of its endpoints, which you can
inspect at `http://<paperless-host>:<port>/api/`. This also documents
most of the available filters and ordering fields.
@@ -162,7 +162,7 @@ specific query parameters cause the API to return full text search
results:
- `/api/documents/?query=your%20search%20query`: Search for a document
using a full text query. For details on the syntax, see [Basic Usage - Searching](usage#basic-usage_searching).
using a full text query. For details on the syntax, see [Basic Usage - Searching](/usage#basic-usage_searching).
- `/api/documents/?more_like=1234`: Search for documents similar to
the document with id 1234.
@@ -225,7 +225,7 @@ Query parameters:
Results returned by the endpoint are ordered by importance of the term
in the document index. The first result is the term that has the highest
Tf/Idf score in the index.
[Tf/Idf](https://en.wikipedia.org/wiki/Tf%E2%80%93idf) score in the index.
```json
["term1", "term3", "term6", "term4"]

View File

@@ -1,5 +1,35 @@
# Changelog
## paperless-ngx 1.10.2
### Features
- Take ownership of k8s-at-home Helm chart [@alexander-bauer](https://github.com/alexander-bauer) ([#1947](https://github.com/paperless-ngx/paperless-ngx/pull/1947))
### Bug Fixes
- Bugfix: Language code checks around two part languages [@stumpylog](https://github.com/stumpylog) ([#2112](https://github.com/paperless-ngx/paperless-ngx/pull/2112))
- Bugfix: Redis socket compatibility didn't handle URLs with ports [@stumpylog](https://github.com/stumpylog) ([#2109](https://github.com/paperless-ngx/paperless-ngx/pull/2109))
- Bugfix: Incompatible URL schemes for socket based Redis [@stumpylog](https://github.com/stumpylog) ([#2092](https://github.com/paperless-ngx/paperless-ngx/pull/2092))
- Fix doc links in contributing [@tooomm](https://github.com/tooomm) ([#2102](https://github.com/paperless-ngx/paperless-ngx/pull/2102))
### Documentation
- Docs: Some more small MkDocs updates [@tooomm](https://github.com/tooomm) ([#2106](https://github.com/paperless-ngx/paperless-ngx/pull/2106))
- Chore: Cleans up documentation links [@stumpylog](https://github.com/stumpylog) ([#2104](https://github.com/paperless-ngx/paperless-ngx/pull/2104))
- Feature: Move docs to material-mkdocs [@shamoon](https://github.com/shamoon) ([#2067](https://github.com/paperless-ngx/paperless-ngx/pull/2067))
- Chore: Add v1.10.1 changelong [@shamoon](https://github.com/shamoon) ([#2082](https://github.com/paperless-ngx/paperless-ngx/pull/2082))
### Maintenance
- Take ownership of k8s-at-home Helm chart [@alexander-bauer](https://github.com/alexander-bauer) ([#1947](https://github.com/paperless-ngx/paperless-ngx/pull/1947))
### All App Changes
- Bugfix: Language code checks around two part languages [@stumpylog](https://github.com/stumpylog) ([#2112](https://github.com/paperless-ngx/paperless-ngx/pull/2112))
- Bugfix: Redis socket compatibility didn't handle URLs with ports [@stumpylog](https://github.com/stumpylog) ([#2109](https://github.com/paperless-ngx/paperless-ngx/pull/2109))
- Bugfix: Incompatible URL schemes for socket based Redis [@stumpylog](https://github.com/stumpylog) ([#2092](https://github.com/paperless-ngx/paperless-ngx/pull/2092))
## paperless-ngx 1.10.1
### Features
@@ -961,11 +991,10 @@ This is a maintenance release.
!!! note
The changed to the full text searching require you to reindex your
documents. _The docker image does this automatically, you don't need to
do anything._ To do this, execute the `document_index reindex`
management command (see `administration-index`{.interpreted-text
role="ref"}).
The changed to the full text searching require you to reindex your
documents. _The docker image does this automatically, you don't need to
do anything._ To do this, execute the `document_index reindex`
management command (see [Managing the document search index](/administration#index)).
### paperless-ng 1.3.2
@@ -1004,8 +1033,7 @@ This release contains new database migrations.
- Changes
- The REST API is versioned from this point onwards. This will
allow me to make changes without breaking existing clients. See
the documentation about `api-versioning`{.interpreted-text
role="ref"} for details.
the documentation about [API versioning](/api#api-versioning) for details.
- Added a color picker for tag colors.
- Added the ability to use the filter for searching the document
content as well.
@@ -1039,7 +1067,7 @@ This release contains new database migrations.
- Changes to the OCRmyPDF integration
- Added support for deskewing and automatic rotation of
incorrectly rotated pages. This is enabled by default, see
`configuration-ocr`{.interpreted-text role="ref"}.
[OCR settings](/configuration#ocr).
- Better support for encrypted files.
- Better support for various other PDF files: Paperless will now
attempt to force OCR with safe options when OCR fails with the
@@ -1066,7 +1094,7 @@ This release contains new database migrations.
- Added a docker-specific configuration option to adjust the number of
worker processes of the web server. See
`configuration-docker`{.interpreted-text role="ref"}.
[Docker options](/configuration#docker).
- Some more memory usage optimizations.
- Don't show inbox statistics if no inbox tag is defined.
@@ -1075,8 +1103,7 @@ This release contains new database migrations.
- Always show top left corner of thumbnails, even for extra wide
documents.
- Added a management command for executing the sanity checker
directly. See `utilities-sanity-checker`{.interpreted-text
role="ref"}.
directly. See [management utilities](/administration#sanity-checker).
- The weekly sanity check now reports messages in the log files.
- Fixed an issue with the metadata tab not reporting anything in case
of missing files.
@@ -1110,7 +1137,7 @@ This release contains new database migrations.
management commands, since these also ensure that they're always
executed as the paperless user and you're less likely to run into
permission issues. See
`utilities-management-commands`{.interpreted-text role="ref"}.
[management commands](/administration#management-commands).
### paperless-ng 1.1.0
@@ -1135,7 +1162,7 @@ This release contains new database migrations.
For status notifications and live updates to work, paperless now
requires an [ASGI](https://asgi.readthedocs.io/en/latest/)-enabled
web server. The docker images uses `gunicorn` and an ASGI-enabled
worker called [uvicorn](http://www.uvicorn.org/), and there is no
worker called [uvicorn](https://www.uvicorn.org/), and there is no
need to configure anything.
For bare metal installations, changes are required for the
@@ -1152,7 +1179,7 @@ This release contains new database migrations.
status notifications.
Apache `mod_wsgi` users, see
`this note <faq-mod_wsgi>`{.interpreted-text role="ref"}.
[this note](/faq#how-do-i-get-websocket-support-with-apache-mod_wsgi).
- Paperless now offers suggestions for tags, correspondents and types
on the document detail page.
@@ -1197,9 +1224,7 @@ bug reports coming in, I think that this is reasonably stable.
- The document exporter locks the media directory and the database
during execution to ensure that the resulting export is
consistent.
- See the
`updated documentation <utilities-exporter>`{.interpreted-text
role="ref"} for more details.
- See the [updated documentation](/administration#exporter) for more details.
- Other changes and additions
- Added a language selector to the settings.
- Added date format options to the settings.
@@ -1288,11 +1313,11 @@ paperless.
- Thanks to [Jo Vandeginste](https://github.com/jovandeginste),
Paperless has optional support for Office documents such as .docx,
.doc, .odt and more.
- See the `configuration<configuration-tika>`{.interpreted-text
role="ref"} on how to enable this feature. This feature requires
two additional services (one for parsing Office documents and
metadata extraction and another for converting Office documents
to PDF), and is therefore not enabled on default installations.
- See the [Tika settings](/configuration#tika) on how to enable this
feature. This feature requires two additional services (one for
parsing Office documents and metadata extraction and another for
converting Office documents to PDF), and is therefore not enabled
on default installations.
- As with all other documents, paperless converts Office documents
to PDF and stores both the original as well as the archived PDF.
- Dark mode
@@ -1368,15 +1393,14 @@ paperless.
!!! note
The bulk delete operations did not update the search index. Therefore,
documents that you deleted remained in the index and caused the search
to return messages about missing documents when searching. Further bulk
operations will properly update the index.
The bulk delete operations did not update the search index. Therefore,
documents that you deleted remained in the index and caused the search
to return messages about missing documents when searching. Further bulk
operations will properly update the index.
However, this change is not retroactive: If you used the delete method
of the bulk editor, you need to reindex your search index by
`running the management command document_index with the argument reindex <administration-index>`{.interpreted-text
role="ref"}.
However, this change is not retroactive: If you used the delete method
of the bulk editor, you need to reindex your search index by
[running the management command `document_index` with the argument `reindex`](/administration#index).
### paperless-ng 0.9.9
@@ -1533,19 +1557,16 @@ primarily.
edit page. If available, a dropdown menu will appear next to the
download button.
- Many of the configuration options regarding OCR have changed.
See `configuration-ocr`{.interpreted-text role="ref"} for
details.
See [OCR settings](/configuration#ocr) for details.
- Paperless no longer guesses the language of your documents. It
always uses the language that you specified with
`PAPERLESS_OCR_LANGUAGE`. Be sure to set this to the language
the majority of your documents are in. Multiple languages can be
specified, but that requires more CPU time.
- The management command
`document_archiver <utilities-archiver>`{.interpreted-text
role="ref"} can be used to create archived versions for already
existing documents.
- The management command [`document_archiver`](/administration#archiver)
can be used to create archived versions for already existing documents.
- Tags from consumption folder.
- Thanks to [jayme-github](http://github.com/jayme-github),
- Thanks to [jayme-github](https://github.com/jayme-github),
paperless now consumes files from sub folders in the consumption
folder and is able to assign tags based on the sub folders a
document was found in. This can be configured with
@@ -1556,7 +1577,7 @@ primarily.
- The endpoint for uploading documents now supports specifying
custom titles, correspondents, tags and types. This can be used
by clients to override the default behavior of paperless. See
`api-file_uploads`{.interpreted-text role="ref"}.
[POSTing documents](/api#file-uploads).
- The document endpoint of API now serves documents in this form:
- correspondents, document types and tags are referenced by
their ID in the fields `correspondent`, `document_type` and
@@ -1590,16 +1611,14 @@ primarily.
- Paperless now supports searching by tags, types and dates and
correspondents. In order to have this applied to your existing
documents, you need to perform a `document_index reindex`
management command (see `administration-index`{.interpreted-text
role="ref"}) that adds the data to the search index. You only
need to do this once, since the schema of the search index
changed. Paperless keeps the index updated after that whenever
something changes.
management command (see [document search index](/administration#index))
that adds the data to the search index. You only need to do this
once, since the schema of the search index changed. Paperless
keeps the index updated after that whenever something changes.
- Paperless now has spelling corrections ("Did you mean") for
miss-typed queries.
- The documentation contains
`information about the query syntax <basic-searching>`{.interpreted-text
role="ref"}.
[information about the query syntax](/usage#basic-usage_searching).
- Front end:
- Clickable tags, correspondents and types allow quick filtering
for related documents.
@@ -1660,10 +1679,8 @@ primarily.
### paperless-ng 0.9.0
- **Deprecated:** GnuPG.
`See this note on the state of GnuPG in paperless-ng. <utilities-encyption>`{.interpreted-text
role="ref"} This features will most likely be removed in future
versions.
- **Deprecated:** GnuPG. [See this note on the state of GnuPG in paperless-ng.](/administration#encryption)
This features will most likely be removed in future versions.
- **Added:** New frontend. Features:
- Single page application: It's much more responsive than the
django admin pages.
@@ -1720,7 +1737,7 @@ primarily.
uses PostgreSQL instead of SQLite. Username, database and
password all default to `paperless` if not specified.
- **Modified \[breaking\]:** document_retagger management command
rework. See `utilities-retagger`{.interpreted-text role="ref"} for
rework. See [Document retagger](/administration#retagger) for
details. Replaces `document_correspondents` management command.
- **Removed \[breaking\]:** Reminders.
- **Removed:** All customizations made to the django admin pages.
@@ -1830,7 +1847,7 @@ primarily.
### 2.5.0
- **New dependency**: Paperless now optimises thumbnail generation
with [optipng](http://optipng.sourceforge.net/), so you'll need to
with [optipng](https://optipng.sourceforge.net/), so you'll need to
install that somewhere in your PATH or declare its location in
`PAPERLESS_OPTIPNG_BINARY`. The Docker image has already been
updated on the Docker Hub, so you just need to pull the latest one

View File

@@ -10,12 +10,10 @@ run paperless, these settings have to be defined in different places.
- If you are running paperless on anything else, paperless will search
for the configuration file in these locations and use the first one
it finds:
```
/path/to/paperless/paperless.conf
/etc/paperless.conf
/usr/local/etc/paperless.conf
```
- The environment variable `PAPERLESS_CONFIGURATION_PATH`
- `/path/to/paperless/paperless.conf`
- `/etc/paperless.conf`
- `/usr/local/etc/paperless.conf`
## Required services
@@ -33,19 +31,19 @@ matcher.
[More information on securing your Redis
Instance](https://redis.io/docs/getting-started/#securing-redis).
Defaults to <redis://localhost:6379>.
Defaults to `redis://localhost:6379`.
`PAPERLESS_DBENGINE=<engine_name>`
: Optional, gives the ability to choose Postgres or MariaDB for
database engine. Available options are [postgresql]{.title-ref} and
[mariadb]{.title-ref}.
database engine. Available options are `postgresql` and
`mariadb`.
Default is [postgresql]{.title-ref}.
Default is `postgresql`.
!!! warning
Using MariaDB comes with some caveats. See [MySQL Caveats](advanced_usage#mysql-caveats).
Using MariaDB comes with some caveats. See [MySQL Caveats](/advanced_usage#mysql-caveats).
`PAPERLESS_DBHOST=<hostname>`
@@ -150,25 +148,38 @@ files created using "collectstatic" manager command are stored.
`PAPERLESS_FILENAME_FORMAT=<format>`
: Changes the filenames paperless uses to store documents in the media
directory. See [File name handling](advanced_usage#file_name_handling) for details.
directory. See [File name handling](/advanced_usage#file-name-handling) for details.
Default is none, which disables this feature.
`PAPERLESS_FILENAME_FORMAT_REMOVE_NONE=<bool>`
: Tells paperless to replace placeholders in
[PAPERLESS_FILENAME_FORMAT]{.title-ref} that would resolve to
`PAPERLESS_FILENAME_FORMAT` that would resolve to
'none' to be omitted from the resulting filename. This also holds
true for directory names. See [File name handling](advanced_usage#file_name_handling) for
true for directory names. See [File name handling](/advanced_usage#file-name-handling) for
details.
Defaults to [false]{.title-ref} which disables this feature.
Defaults to `false` which disables this feature.
`PAPERLESS_LOGGING_DIR=<path>`
: This is where paperless will store log files.
Defaults to "`PAPERLESS_DATA_DIR`/log/".
Defaults to `PAPERLESS_DATA_DIR/log/`.
`PAPERLESS_NLTK_DIR=<path>`
: This is where paperless will search for the data required for NLTK
processing, if you are using it. If you are using the Docker image,
this should not be changed, as the data is included in the image
already.
Previously, the location defaulted to `PAPERLESS_DATA_DIR/nltk`.
Unless you are using this in a bare metal install or other setup,
this folder is no longer needed and can be removed manually.
Defaults to `/usr/local/share/nltk_data`
## Logging
@@ -283,10 +294,10 @@ login with the selected user.
: If this environment variable is specified, Paperless automatically
creates a superuser with the provided username at start. This is
useful in cases where you can not run the
[createsuperuser]{.title-ref} command separately, such as Kubernetes
`createsuperuser` command separately, such as Kubernetes
or AWS ECS.
Requires [PAPERLESS_ADMIN_PASSWORD]{.title-ref} to be set.
Requires PAPERLESS_ADMIN_PASSWORD be set.
!!! note
@@ -297,13 +308,13 @@ or AWS ECS.
`PAPERLESS_ADMIN_MAIL=<email>`
: (Optional) Specify superuser email address. Only used when
[PAPERLESS_ADMIN_USER]{.title-ref} is set.
PAPERLESS_ADMIN_USER is set.
Defaults to `root@localhost`.
`PAPERLESS_ADMIN_PASSWORD=<password>`
: Only used when [PAPERLESS_ADMIN_USER]{.title-ref} is set. This will
: Only used when PAPERLESS_ADMIN_USER is set. This will
be the password of the automatically created superuser.
`PAPERLESS_COOKIE_PREFIX=<str>`
@@ -331,26 +342,25 @@ applications.
If you're exposing paperless to the internet directly, do not use
this.
Also see the warning [in the official documentation
<https://docs.djangoproject.com/en/3.1/howto/auth-remote-user/#configuration>]{.title-ref}.
Also see the warning [in the official documentation](https://docs.djangoproject.com/en/3.1/howto/auth-remote-user/#configuration).
Defaults to [false]{.title-ref} which disables this feature.
Defaults to "false" which disables this feature.
`PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME=<str>`
: If [PAPERLESS_ENABLE_HTTP_REMOTE_USER]{.title-ref} is enabled, this
: If "PAPERLESS*ENABLE_HTTP_REMOTE_USER" is enabled, this
property allows to customize the name of the HTTP header from which
the authenticated username is extracted. Values are in terms of
\[HttpRequest.META\](<https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpRequest.META>).
Thus, the configured value must start with [HTTP\_]{.title-ref}
[HttpRequest.META](https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpRequest.META).
Thus, the configured value must start with `HTTP*`
followed by the normalized actual header name.
Defaults to [HTTP_REMOTE_USER]{.title-ref}.
Defaults to "HTTP_REMOTE_USER".
`PAPERLESS_LOGOUT_REDIRECT_URL=<str>`
: URL to redirect the user to after a logout. This can be used
together with [PAPERLESS_ENABLE_HTTP_REMOTE_USER]{.title-ref} to
together with PAPERLESS_ENABLE_HTTP_REMOTE_USER to
redirect the user back to the SSO application's logout page.
Defaults to None, which disables this feature.
@@ -368,7 +378,7 @@ needs.
parsing documents.
It should be a 3-letter language code consistent with ISO 639:
<https://www.loc.gov/standards/iso639-2/php/code_list.php>
https://www.loc.gov/standards/iso639-2/php/code_list.php
Set this to the language most of your documents are written in.
@@ -565,8 +575,10 @@ they use underscores instead of dashes.
Paperless can make use of [Tika](https://tika.apache.org/) and
[Gotenberg](https://gotenberg.dev/) for parsing and converting
"Office" documents (such as ".doc", ".xlsx" and ".odt"). If you
wish to use this, you must provide a Tika server and a Gotenberg server,
"Office" documents (such as ".doc", ".xlsx" and ".odt").
Tika and Gotenberg are also needed to allow parsing of E-Mails (.eml).
If you wish to use this, you must provide a Tika server and a Gotenberg server,
configure their endpoints, and enable the feature.
`PAPERLESS_TIKA_ENABLED=<bool>`
@@ -605,14 +617,17 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
# ...
# ...
gotenberg:
image: gotenberg/gotenberg:7.6
restart: unless-stopped
command:
- 'gotenberg'
- '--chromium-disable-routes=true'
gotenberg:
image: gotenberg/gotenberg:7.6
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
command:
- 'gotenberg'
- '--chromium-disable-javascript=true'
- '--chromium-allow-list=file:///tmp/.*'
tika:
image: ghcr.io/paperless-ngx/tika:latest
@@ -624,8 +639,7 @@ Add the configuration variables to the environment of the webserver
and add the additional services below the webserver service. Watch out
for indentation.
Make sure to use the correct format [PAPERLESS_TIKA_ENABLED =
1]{.title-ref} so python_dotenv can parse the statement correctly.
Make sure to use the correct format `PAPERLESS_TIKA_ENABLED = 1` so python_dotenv can parse the statement correctly.
## Software tweaks {#software_tweaks}
@@ -648,7 +662,7 @@ paperless will process in parallel on a single document.
Ensure that the product
`PAPERLESS_TASK_WORKERS \: PAPERLESS_THREADS_PER_WORKER`
`PAPERLESS_TASK_WORKERS * PAPERLESS_THREADS_PER_WORKER`
does not exceed your CPU core count or else paperless will be
extremely slow. If you want paperless to process many documents in
@@ -660,7 +674,7 @@ paperless will process in parallel on a single document.
count, with a slight favor towards threads per worker:
| CPU core count | Workers | Threads |
|----------------|---------|---------|
| -------------- | ------- | ------- |
| > 1 | > 1 | > 1 |
| > 2 | > 2 | > 1 |
| > 4 | > 2 | > 2 |
@@ -693,6 +707,16 @@ for details on how to set it.
Defaults to UTC.
`PAPERLESS_ENABLE_NLTK=<bool>`
: Enables or disables the advanced natural language processing
used during automatic classification. If disabled, paperless will
still preform some basic text pre-processing before matching.
See also `PAPERLESS_NLTK_DIR`.
Defaults to 1.
## Polling {#polling}
`PAPERLESS_CONSUMER_POLLING=<num>`
@@ -752,7 +776,7 @@ consumption directory as well.
`PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=<bool>`
: Set the names of subdirectories as tags for consumed files. E.g.
<CONSUMPTION_DIR>/foo/bar/file.pdf will add the tags "foo" and
`<CONSUMPTION_DIR>/foo/bar/file.pdf` will add the tags "foo" and
"bar" to the consumed file. Paperless will create any tags that
don't exist yet.
@@ -827,7 +851,7 @@ documents.
: After a document is consumed, Paperless can trigger an arbitrary
script if you like. This script will be passed a number of arguments
for you to work with. For more information, take a look at [Post-consumption script](advanced_usage#post_consume_script).
for you to work with. For more information, take a look at [Post-consumption script](/advanced_usage#post-consume-script).
The default is blank, which means nothing will be executed.
@@ -841,8 +865,7 @@ option as specified in
The filename will be checked first, and if nothing is found, the
document text will be checked as normal.
A date in a filename must have some separators ([.]{.title-ref},
[-]{.title-ref}, [/]{.title-ref}, etc) for it to be parsed.
A date in a filename must have some separators (`.`, `,`, `-`, `/`, etc) for it to be parsed.
Defaults to none, which disables this feature.
@@ -928,7 +951,7 @@ the literal path for that program.
These options don't have any effect in `paperless.conf`. These options
adjust the behavior of the docker container. Configure these in
[docker-compose.env]{.title-ref}.
`docker-compose.env`.
`PAPERLESS_WEBSERVER_WORKERS=<num>`
@@ -946,7 +969,7 @@ increase RAM usage.
There are special setups where you may need to configure this value
to restrict the Ip address or interface the webserver listens on.
Defaults to \[::\], meaning all interfaces, including IPv6.
Defaults to `[::]`, meaning all interfaces, including IPv6.
`PAPERLESS_PORT=<port>`
@@ -1017,7 +1040,7 @@ configuration option:
[Flower](https://flower.readthedocs.io/en/latest/index.html) will be
started by the container.
You can read more about this in the [advanced documentation](advanced#celery-monitoring).
You can read more about this in the [advanced documentation](/advanced_usage#celery-monitoring).
## Update Checking {#update-checking}

View File

@@ -39,16 +39,16 @@ guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTIN
## Code formatting with pre-commit Hooks
To ensure a consistent style and formatting across the project source,
the project utilizes a Git [pre-commit]{.title-ref} hook to perform some
formatting and linting before a commit is allowed. That way, everyone
uses the same style and some common issues can be caught early on. See
below for installation instructions.
the project utilizes a Git [`pre-commit`](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
hook to perform some formatting and linting before a commit is allowed.
That way, everyone uses the same style and some common issues can be caught
early on. See below for installation instructions.
Once installed, hooks will run when you commit. If the formatting isn't
quite right or a linter catches something, the commit will be rejected.
You'll need to look at the output and fix the issue. Some hooks, such
as the Python formatting tool [black]{.title-ref}, will format failing
files, so all you need to do is [git add]{.title-ref} those files again
as the Python formatting tool `black`, will format failing
files, so all you need to do is `git add` those files again
and retry your commit.
## Initial setup and first start
@@ -58,7 +58,7 @@ first-time setup. To do the setup you need to perform the steps from the
following chapters in a certain order:
1. Install prerequisites + pipenv as mentioned in
`[Bare metal route](/setup#bare_metal)
[Bare metal route](/setup#bare_metal)
2. Copy `paperless.conf.example` to `paperless.conf` and enable debug
mode.
@@ -69,7 +69,7 @@ following chapters in a certain order:
$ npm install -g @angular/cli
```
4. Install pre-commit
4. Install pre-commit hooks
```shell-session
pre-commit install
@@ -81,7 +81,7 @@ following chapters in a certain order:
mkdir -p consume media
```
6. You can now either \...
6. You can now either ...
- install redis or
@@ -91,9 +91,9 @@ following chapters in a certain order:
- spin up a bare redis container
> ```shell-session
> docker run -d -p 6379:6379 --restart unless-stopped redis:latest
> ```
```shell-session
docker run -d -p 6379:6379 --restart unless-stopped redis:latest
```
7. Install the python dependencies by performing in the src/ directory.
@@ -101,10 +101,12 @@ following chapters in a certain order:
pipenv install --dev
```
> - Make sure you're using python 3.9.x or lower. Otherwise you might
> get issues with building dependencies. You can use
> [pyenv](https://github.com/pyenv/pyenv) to install a specific
> python version.
!!! note
Make sure you're using python 3.10.x or lower. Otherwise you might
get issues with building dependencies. You can use
[pyenv](https://github.com/pyenv/pyenv) to install a specific
python version.
8. Generate the static UI so you can perform a login to get session
that is required for frontend development (this needs to be done one
@@ -126,9 +128,9 @@ following chapters in a certain order:
you're developing for, you need to have some or all of them
running.
> ```shell-session
> python3 manage.py runserver & python3 manage.py document_consumer & celery --app paperless worker
> ```
```shell-session
python3 manage.py runserver & python3 manage.py document_consumer & celery --app paperless worker
```
11. Login with the superuser credentials provided in step 8 at
`http://localhost:8000` to create a session that enables you to use
@@ -140,15 +142,15 @@ development go to `/src-ui` and run `ng serve`. From there you can use
## Back end development
The backend is a django application. PyCharm works well for development,
The backend is a [Django](https://www.djangoproject.com/) application. PyCharm works well for development,
but you can use whatever you want.
Configure the IDE to use the src/ folder as the base source folder.
Configure the following launch configurations in your IDE:
- python3 manage.py runserver
- celery \--app paperless worker
- python3 manage.py document_consumer
- `python3 manage.py runserver`
- `celery --app paperless worker`
- `python3 manage.py document_consumer`
To start them all:
@@ -158,24 +160,26 @@ python3 manage.py runserver & python3 manage.py document_consumer & celery --app
Testing and code style:
- Run `pytest` in the src/ directory to execute all tests. This also
- Run `pytest` in the `src/` directory to execute all tests. This also
generates a HTML coverage report. When runnings test, paperless.conf
is loaded as well. However: the tests rely on the default
configuration. This is not ideal. But for now, make sure no settings
except for DEBUG are overridden when testing.
- Coding style is enforced by the Git pre-commit hooks. These will
ensure your code is formatted and do some linting when you do a [git
commit]{.title-ref}.
ensure your code is formatted and do some linting when you do a `git commit`.
- You can also run `black` manually to format your code
!!! note
- The `pre-commit` hooks will modify files and interact with each other.
It may take a couple of `git add`, `git commit` cycle to satisfy them.
!!! note
The line length rule E501 is generally useful for getting multiple
source files next to each other on the screen. However, in some
cases, its just not possible to make some lines fit, especially
complicated IF cases. Append `# NOQA: E501` to disable this check
complicated IF cases. Append `# noqa: E501` to disable this check
for certain lines.
## Front end development
@@ -353,7 +357,8 @@ LANGUAGES = [
## Building the documentation
The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/). If you want to build the documentation locally, this is how you do it:
The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/).
If you want to build the documentation locally, this is how you do it:
1. Install python dependencies.
@@ -366,7 +371,7 @@ The documentation is built using material-mkdocs, see their [documentation](http
```shell-session
$ cd /path/to/paperless
$ pipenv mkdocs build
$ pipenv mkdocs build --config-file mkdocs.yml
```
## Building the Docker image
@@ -379,9 +384,9 @@ helper script `build-docker-image.sh`.
Building the docker image from source:
> ```shell-session
> ./build-docker-image.sh Dockerfile -t <your-tag>
> ```
```shell-session
./build-docker-image.sh Dockerfile -t <your-tag>
```
## Extending Paperless
@@ -428,7 +433,7 @@ class MyCustomParser(DocumentParser):
def get_thumbnail(self, document_path, mime_type):
# This should return the path to a thumbnail you created for this
# document.
return os.path.join(self.tempdir, "thumb.png")
return os.path.join(self.tempdir, "thumb.webp")
```
If you encounter any issues during parsing, raise a

View File

@@ -1,6 +1,6 @@
# Frequently Asked Questions
### _What's the general plan for Paperless-ngx?_
## _What's the general plan for Paperless-ngx?_
**A:** While Paperless-ngx is already considered largely
"feature-complete" it is a community-driven project and development
@@ -9,7 +9,7 @@ discussions and "up-voted" by the community but this is not a
guarantee the feature will be implemented. This project will always be
open to collaboration in the form of PRs, ideas etc.
### _I'm using docker. Where are my documents?_
## _I'm using docker. Where are my documents?_
**A:** Your documents are stored inside the docker volume
`paperless_media`. Docker manages this volume automatically for you. It
@@ -27,9 +27,7 @@ system. On Linux, chances are high that this location is
files around manually. This folder is meant to be entirely managed by
docker and paperless.
### Let's say I want to switch tools in a year. Can I easily move
to other systems?\*
## Let's say I want to switch tools in a year. Can I easily move to other systems?
**A:** Your documents are stored as plain files inside the media folder.
You can always drag those files out of that folder to use them
@@ -41,27 +39,27 @@ elsewhere. Here are a couple notes about that.
- By default, paperless uses the internal ID of each document as its
filename. This might not be very convenient for export. However, you
can adjust the way files are stored in paperless by
[configuring the filename format](advanced_usage#file_name_handling).
- [The exporter](administration#exporter) is
[configuring the filename format](/advanced_usage#file-name-handling).
- [The exporter](/administration#exporter) is
another easy way to get your files out of paperless with reasonable
file names.
### _What file types does paperless-ngx support?_
## _What file types does paperless-ngx support?_
**A:** Currently, the following files are supported:
- PDF documents, PNG images, JPEG images, TIFF images and GIF images
are processed with OCR and converted into PDF documents.
- PDF documents, PNG images, JPEG images, TIFF images, GIF images and
WebP images are processed with OCR and converted into PDF documents.
- Plain text documents are supported as well and are added verbatim to
paperless.
- With the optional Tika integration enabled (see [Tika configuration](configuration#tika),
- With the optional Tika integration enabled (see [Tika configuration](/configuration#tika),
Paperless also supports various Office documents (.docx, .doc, odt,
.ppt, .pptx, .odp, .xls, .xlsx, .ods).
Paperless-ngx determines the type of a file by inspecting its content.
The file extensions do not matter.
### _Will paperless-ngx run on Raspberry Pi?_
## _Will paperless-ngx run on Raspberry Pi?_
**A:** The short answer is yes. I've tested it on a Raspberry Pi 3 B.
The long answer is that certain parts of Paperless will run very slow,
@@ -73,11 +71,11 @@ has to do much less work to serve the data.
!!! note
You can adjust some of the settings so that paperless uses less
processing power. See [setup](setup#less_powerful_devices) for details.
processing power. See [setup](/setup#less-powerful-devices) for details.
### _How do I install paperless-ngx on Raspberry Pi?_
## _How do I install paperless-ngx on Raspberry Pi?_
**A:** Docker images are available for arm and arm64 hardware, so just
**A:** Docker images are available for armv7 and arm64 hardware, so just
follow the docker-compose instructions. Apart from more required disk
space compared to a bare metal installation, docker comes with close to
zero overhead, even on Raspberry Pi.
@@ -87,13 +85,13 @@ the python requirements do not have precompiled packages for ARM /
ARM64. Installation of these will require additional development
libraries and compilation will take a long time.
### _How do I run this on Unraid?_
## _How do I run this on Unraid?_
**A:** Paperless-ngx is available as [community
app](https://unraid.net/community/apps?q=paperless-ngx) in Unraid. [Uli
Fahrer](https://github.com/Tooa) created a container template for that.
### _How do I run this on my toaster?_
## _How do I run this on my toaster?_
**A:** I honestly don't know! As for all other devices that might be
able to run paperless, you're a bit on your own. If you can't run the
@@ -103,11 +101,11 @@ This is also what I use to test new releases with. Apart from that, I
also have a Raspberry Pi, which I occasionally build the image on and
see if it works.
### _How do I proxy this with NGINX?_
## _How do I proxy this with NGINX?_
**A:** See [here](setup#nginx).
**A:** See [here](/setup#nginx).
### _How do I get WebSocket support with Apache mod_wsgi_?
## _How do I get WebSocket support with Apache mod_wsgi_?
**A:** `mod_wsgi` by itself does not support ASGI. Paperless will
continue to work with WSGI, but certain features such as status

View File

@@ -5,7 +5,7 @@
**Paperless-ngx** is a _community-supported_ open-source document management system that transforms your
physical documents into a searchable online archive so you can keep, well, _less paper_.
[Get started](/setup/){ .md-button .md-button--primary .index-callout }
[Get started](/setup){ .md-button .md-button--primary .index-callout }
[Demo](https://demo.paperless-ngx.com){ .md-button .md-button--secondary target=\_blank }
</div>
@@ -50,12 +50,12 @@ If you want to learn about what's different in paperless-ngx from
Paperless, check out these resources in the documentation:
- [Some screenshots](#screenshots) of the new UI are available.
- Read [this section](/advanced_usage/#advanced-automatic_matching) if you want to learn about how paperless automates all
- Read [this section](/advanced_usage#automatic-matching) if you want to learn about how paperless automates all
tagging using machine learning.
- Paperless now comes with a [proper email consumer](/usage/#usage-email) that's fully tested and production ready.
- Paperless now comes with a [proper email consumer](/usage#usage-email) that's fully tested and production ready.
- Paperless creates searchable PDF/A documents from whatever you put into the consumption directory. This means
that you can select text in image-only documents coming from your scanner.
- See [this note](/administration/#utilities-encyption) about GnuPG encryption in paperless-ngx.
- See [this note](/administration#encryption) about GnuPG encryption in paperless-ngx.
- Paperless is now integrated with a
[task processing queue](/setup#task_processor) that tells you at a glance when and why something is not working.
- The [changelog](/changelog) contains a detailed list of all changes in paperless-ngx.

View File

@@ -2,10 +2,10 @@
You can go multiple routes to setup and run Paperless:
- [Use the easy install docker script](/setup#docker_script)
- [Pull the image from Docker Hub](/setup#docker_hub)
- [Build the Docker image yourself](/setup#docker_build)
- [Install Paperless directly on your system manually (bare metal)](/setup#bare_metal)
- [Use the easy install docker script](#docker_script)
- [Pull the image from Docker Hub](#docker_hub)
- [Build the Docker image yourself](#docker_build)
- [Install Paperless directly on your system manually (bare metal)](#bare_metal)
The Docker routes are quick & easy. These are the recommended routes.
This configures all the stuff from the above automatically so that it
@@ -234,9 +234,8 @@ steps described in [Docker setup](#docker_hub) automatically.
certain, more updated software. If you want to build these images
your self, that is possible, but beyond the scope of these steps.
4. Follow steps 3 to 8 of [Docker Setup](setup#docker_hub)
role="ref"}. When asked to run `docker-compose pull` to pull the
image, do
4. Follow steps 3 to 8 of [Docker Setup](#docker_hub). When asked to run
`docker-compose pull` to pull the image, do
```shell-session
$ docker-compose build
@@ -316,20 +315,21 @@ supported.
enabled. This is usually the case, but not always.
4. Get the release archive from
<https://github.com/paperless-ngx/paperless-ngx/releases>. If you
clone the git repo as it is, you also have to compile the front end
by yourself. Extract the archive to a place from where you wish to
execute it, such as `/opt/paperless`.
<https://github.com/paperless-ngx/paperless-ngx/releases>. Extract the
archive to a place from where you wish to execute it, such as
`/opt/paperless`. If you clone the git repo as it is, you also have to
compile the frontend yourself, see [here](/development#front-end-development)
and use the `build` step, not `serve`.
5. Configure paperless. See [configuration](configuration) for details.
5. Configure paperless. See [configuration](/configuration) for details.
Edit the included `paperless.conf` and adjust the settings to your
needs. Required settings for getting
paperless running are:
- `PAPERLESS_REDIS` should point to your redis server, such as
<redis://localhost:6379>.
- `PAPERLESS_DBENGINE` optional, and should be one of [postgres,
mariadb, or sqlite]{.title-ref}
- `PAPERLESS_DBENGINE` optional, and should be one of `postgres`,
`mariadb`, or `sqlite`
- `PAPERLESS_DBHOST` should be the hostname on which your
PostgreSQL server is running. Do not configure this to use
SQLite instead. Also configure port, database name, user and
@@ -344,7 +344,7 @@ supported.
allows third parties to forge authentication credentials.
- `PAPERLESS_URL` if you are behind a reverse proxy. This should
point to your domain. Please see
[configuration](configuration) for more
[configuration](/configuration) for more
information.
Many more adjustments can be made to paperless, especially the OCR
@@ -387,17 +387,17 @@ supported.
9. Go to `/opt/paperless/src`, and execute the following commands:
```bash
\# This creates the database schema.
# This creates the database schema.
sudo -Hu paperless python3 manage.py migrate
\# This creates your first paperless user
# This creates your first paperless user
sudo -Hu paperless python3 manage.py createsuperuser
```
10. Optional: Test that paperless is working by executing
```bash
\# This collects static files from paperless and django.
# This collects static files from paperless and django.
sudo -Hu paperless python3 manage.py runserver
```
@@ -480,7 +480,7 @@ supported.
not available for most distributions.
15. Optional: If using the NLTK machine learning processing (see
`PAPERLESS_ENABLE_NLTK` in [configuration](configuration#software_tweaks) for details),
`PAPERLESS_ENABLE_NLTK` in [configuration](/configuration#software_tweaks) for details),
download the NLTK data for the Snowball
Stemmer, Stopwords and Punkt tokenizer to your
`PAPERLESS_DATA_DIR/nltk`. Refer to the [NLTK
@@ -562,7 +562,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
the docker-compose files from
[here](https://github.com/paperless-ngx/paperless-ngx/tree/master/docker/compose)
or clone the repository to build the image yourself (see
[above](/setup#docker_build)). You can
[above](#docker_build)). You can
either replace your current paperless folder or put paperless-ngx in
a different location.
@@ -586,7 +586,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
after you migrated your existing SQLite database.
5. Adjust `docker-compose.yml` and `docker-compose.env` to your needs.
See [Docker setup](setup#docker_hub) details on
See [Docker setup](#docker_hub) details on
which edits are advised.
6. [Update paperless.](/administration#updating)
@@ -676,7 +676,7 @@ below use PostgreSQL, but are applicable to MySQL/MariaDB with the
!!! warning
MySQL is case insensitive by default, treating values like "Name" and
"NAME" as identical. See [MySQL caveats](advanced##mysql-caveats) for details.
"NAME" as identical. See [MySQL caveats](/advanced_usage#mysql-caveats) for details.
!!! warning
@@ -691,7 +691,7 @@ below use PostgreSQL, but are applicable to MySQL/MariaDB with the
file to `docker-compose.yml`. Remember to adjust the consumption
directory, if necessary.
b) Without docker, configure the database in your `paperless.conf`
file. See [configuration](configuration) for
file. See [configuration](/configuration) for
details.
3. Open a shell and initialize the database:
@@ -766,7 +766,7 @@ After that, you need to clear your cookies (Paperless-ngx comes with
updated dependencies that do cookie-processing differently) and probably
your cache as well.
# Considerations for less powerful devices {#less_powerful_devices}
# Considerations for less powerful devices {#less-powerful-devices}
Paperless runs on Raspberry Pi. However, some things are rather slow on
the Pi and configuring some options in paperless can help improve
@@ -797,12 +797,12 @@ performance immensely:
more advanced language processing, which can take more memory and
processing time.
For details, refer to [configuration](configuration).
For details, refer to [configuration](/configuration).
!!! note
Updating the
[automatic matching algorithm](/advanced_usage#automatic_matching) takes quite a bit of time. However, the update mechanism
[automatic matching algorithm](/advanced_usage#automatic-matching) takes quite a bit of time. However, the update mechanism
checks if your data has changed before doing the heavy lifting. If you
experience the algorithm taking too much cpu time, consider changing the
schedule in the admin interface to daily. You can also manually invoke
@@ -849,7 +849,7 @@ http {
```
The `PAPERLESS_URL` configuration variable is also required when using a
reverse proxy. Please refer to the [hosting and security](configuration#hosting-and-security) docs.
reverse proxy. Please refer to the [hosting and security](/configuration#hosting-and-security) docs.
Also read
[this](https://channels.readthedocs.io/en/stable/deploying.html#nginx-supervisor-ubuntu),

View File

@@ -32,7 +32,7 @@ If you find the OCR accuracy to be too low, and/or the document consumer
warns that
`OCR for XX failed, but we're going to stick with what we've got since FORGIVING_OCR is enabled`,
then you might need to install the [Tesseract language
files](http://packages.ubuntu.com/search?keywords=tesseract-ocr)
files](https://packages.ubuntu.com/search?keywords=tesseract-ocr)
marching your document's languages.
As an example, if you are running Paperless-ngx from any Ubuntu or
@@ -125,13 +125,13 @@ using docker-compose, this is achieved by the following configuration
change in the `docker-compose.yml` file:
```yaml
gotenberg:
image: gotenberg/gotenberg:7.6
restart: unless-stopped
command:
- 'gotenberg'
- '--chromium-disable-routes=true'
- '--api-timeout=60'
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
command:
- 'gotenberg'
- '--chromium-disable-javascript=true'
- '--chromium-allow-list=file:///tmp/.*'
- '--api-timeout=60'
```
## Permission denied errors in the consumption directory
@@ -145,7 +145,7 @@ The following error occured while consuming document.pdf: [Errno 13] Permission
This happens when paperless does not have permission to delete files
inside the consumption directory. Ensure that `USERMAP_UID` and
`USERMAP_GID` are set to the user id and group id you use on the host
operating system, if these are different from `1000`. See [Docker setup](setup#docker_hub).
operating system, if these are different from `1000`. See [Docker setup](/setup#docker_hub).
Also ensure that you are able to read and write to the consumption
directory on the host.
@@ -222,7 +222,7 @@ This might have multiple reasons.
SENDFILE=0
```
to your [docker-compose.env]{.title-ref} file.
to your `docker-compose.env` file.
## Error while reading metadata
@@ -326,9 +326,9 @@ unlock. This may have minor performance implications.
## gunicorn fails to start with "is not a valid port number"
You are likely running using Kubernetes, which automatically creates an
environment variable named [\${serviceName}\_PORT]{.title-ref}. This is
environment variable named `${serviceName}_PORT`. This is
the same environment variable which is used by Paperless to optionally
change the port gunicorn listens on.
To fix this, set [PAPERLESS_PORT]{.title-ref} again to your desired
port, or the default of 8000.
To fix this, set `PAPERLESS_PORT` again to your desired port, or the
default of 8000.

View File

@@ -1,9 +1,9 @@
# Usage Overview
Paperless is an application that manages your personal documents. With
the help of a document scanner (see [the scanners wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations)), paperless transforms your wieldy physical document binders
into a searchable archive and provides many utilities for finding and
managing your documents.
the help of a document scanner (see [the scanners wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations)),
paperless transforms your unwieldy physical document binders into a searchable archive
and provides many utilities for finding and managing your documents.
## Terms and definitions
@@ -37,7 +37,7 @@ Each document has a couple of fields that you can assign to them:
date you signed a contract, or the date a letter was sent to you.
- The _archive serial number_ (short: ASN) of a document is the
identifier of the document in your physical document binders. See
[recommended workflow](#usage-reccomended_workflow) below.
[recommended workflow](#usage-recommended-workflow) below.
- The _content_ of a document is the text that was OCR'ed from the
document. This text is fed into the search engine and is used for
matching tags, correspondents and document types.
@@ -74,8 +74,8 @@ following operations on your documents:
### The consumption directory
The primary method of getting documents into your database is by putting
them in the consumption directory. The consumer runs in an infinite
loop, looking for new additions to this directory. When it finds them,
them in the consumption directory. The consumer waits patiently, looking
for new additions to this directory. When it finds them,
the consumer goes about the process of parsing them with the OCR,
indexing what it finds, and storing it in the media directory.
@@ -86,7 +86,7 @@ scanner to automatically push files to this directory, you'll need to
setup some sort of service to accept the files from the scanner.
Typically, you're looking at an FTP server like
[Proftpd](http://www.proftpd.org/) or a Windows folder share with
[Samba](http://www.samba.org/).
[Samba](https://www.samba.org/).
### Web UI Upload
@@ -99,7 +99,7 @@ dragging-and-dropping files into your browser window.
### Mobile upload {#usage-mobile_upload}
The mobile app over at <https://github.com/qcasey/paperless_share>
The mobile app over at [https://github.com/qcasey/paperless_share](https://github.com/qcasey/paperless_share)
allows Android users to share any documents with paperless. This can be
combined with any of the mobile scanning apps out there, such as Office
Lens.
@@ -195,7 +195,7 @@ configured on the 'Scheduled tasks' page in the admin.
### REST API
You can also submit a document using the REST API, see [docs][api#file-uploads]
You can also submit a document using the REST API, see [POSTing documents](/api#file-uploads)
for details.
## Best practices {#basic-searching}
@@ -325,7 +325,7 @@ language](https://whoosh.readthedocs.io/en/latest/querylang.html). For
details on what date parsing utilities are available, see [Date
parsing](https://whoosh.readthedocs.io/en/latest/dates.html#parsing-date-queries).
## The recommended workflow {#usage-recommended_workflow}
## The recommended workflow {#usage-recommended-workflow}
Once you have familiarized yourself with paperless and are ready to use
it for all your documents, the recommended workflow for managing your
@@ -398,7 +398,7 @@ Once you have scanned in a document, proceed in paperless as follows.
paperless will assign them automatically. After consuming a couple
documents, you can even ask paperless to *learn* when to assign tags and
correspondents by itself. For details on this feature, see
[advanced matching](advanced_usage#matching).
[advanced matching](/advanced_usage#matching).
### Task management
@@ -416,7 +416,7 @@ how regularly you intend to scan documents and use paperless.
performed the task associated with the document, move it to the
inbox.
## Architectue
## Architecture
Paperless-ngx consists of the following components:

View File

@@ -23,6 +23,7 @@ theme:
- navigation.tabs
- navigation.top
- toc.integrate
- content.code.annotate
icon:
repo: fontawesome/brands/github
favicon: assets/favicon.png
@@ -39,6 +40,8 @@ markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- pymdownx.inlinehilite
strict: true
nav:
- index.md
- setup.md

View File

@@ -2,5 +2,5 @@
docker run -p 5432:5432 -e POSTGRES_PASSWORD=password -v paperless_pgdata:/var/lib/postgresql/data -d postgres:13
docker run -d -p 6379:6379 redis:latest
docker run -p 3000:3000 -d gotenberg/gotenberg:7.6
docker run -p 3000:3000 -d gotenberg/gotenberg:7.6 gotenberg --chromium-disable-javascript=true --chromium-allow-list="file:///tmp/.*"
docker run -p 9998:9998 -d ghcr.io/paperless-ngx/tika:latest

51
src-ui/.eslintrc.json Normal file
View File

@@ -0,0 +1,51 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@@ -1,179 +1,196 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"paperless-ui": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"i18n": {
"sourceLocale": "en-US",
"locales": {
"be-BY": "src/locale/messages.be_BY.xlf",
"cs-CZ": "src/locale/messages.cs_CZ.xlf",
"da-DK": "src/locale/messages.da_DK.xlf",
"de-DE": "src/locale/messages.de_DE.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"es-ES": "src/locale/messages.es_ES.xlf",
"fr-FR": "src/locale/messages.fr_FR.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"pl-PL": "src/locale/messages.pl_PL.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"pt-PT": "src/locale/messages.pt_PT.xlf",
"ro-RO": "src/locale/messages.ro_RO.xlf",
"ru-RU": "src/locale/messages.ru_RU.xlf",
"sl-SI": "src/locale/messages.sl_SI.xlf",
"sr-CS": "src/locale/messages.sr_CS.xlf",
"sv-SE": "src/locale/messages.sv_SE.xlf",
"tr-TR": "src/locale/messages.tr_TR.xlf",
"zh-CN": "src/locale/messages.zh_CN.xlf"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/paperless-ui",
"outputHashing": "none",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"localize": true,
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest", {
"glob": "pdf.worker.min.js",
"input": "node_modules/pdfjs-dist/build/",
"output": "/assets/js/"
}
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"allowedCommonJsDependencies": [
"ng2-pdf-viewer"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "../src/documents/static/frontend/",
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
},
"en-US": {
"localize": ["en-US"]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build:en-US"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
}
}
}
},
"defaultProject": "paperless-ui"
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"paperless-ui": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"i18n": {
"sourceLocale": "en-US",
"locales": {
"be-BY": "src/locale/messages.be_BY.xlf",
"cs-CZ": "src/locale/messages.cs_CZ.xlf",
"da-DK": "src/locale/messages.da_DK.xlf",
"de-DE": "src/locale/messages.de_DE.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"es-ES": "src/locale/messages.es_ES.xlf",
"fr-FR": "src/locale/messages.fr_FR.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"pl-PL": "src/locale/messages.pl_PL.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"pt-PT": "src/locale/messages.pt_PT.xlf",
"ro-RO": "src/locale/messages.ro_RO.xlf",
"ru-RU": "src/locale/messages.ru_RU.xlf",
"sl-SI": "src/locale/messages.sl_SI.xlf",
"sr-CS": "src/locale/messages.sr_CS.xlf",
"sv-SE": "src/locale/messages.sv_SE.xlf",
"tr-TR": "src/locale/messages.tr_TR.xlf",
"zh-CN": "src/locale/messages.zh_CN.xlf"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/paperless-ui",
"outputHashing": "none",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"localize": true,
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest",
{
"glob": "pdf.worker.min.js",
"input": "node_modules/pdfjs-dist/build/",
"output": "/assets/js/"
}
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"allowedCommonJsDependencies": [
"ng2-pdf-viewer"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "../src/documents/static/frontend/",
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
},
"en-US": {
"localize": [
"en-US"
]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build:en-US"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
},
"defaultProject": "paperless-ui",
"cli": {
"schematicCollections": [
"@angular-eslint/schematics"
]
}
}

View File

@@ -35,6 +35,16 @@ describe('settings', () => {
req.reply(response)
}
).as('savedViews')
cy.intercept('http://localhost:8000/api/mail_accounts/*', {
fixture: 'mail_accounts/mail_accounts.json',
})
cy.intercept('http://localhost:8000/api/mail_rules/*', {
fixture: 'mail_rules/mail_rules.json',
}).as('mailRules')
cy.intercept('http://localhost:8000/api/tasks/', {
fixture: 'tasks/tasks.json',
})
})
cy.fixture('documents/documents.json').then((documentsJson) => {
@@ -48,7 +58,6 @@ describe('settings', () => {
cy.viewport(1024, 1600)
cy.visit('/settings')
cy.wait('@savedViews')
})
it('should activate / deactivate save button when settings change and are saved', () => {
@@ -64,7 +73,7 @@ describe('settings', () => {
cy.contains('a', 'Dashboard').click()
cy.contains('You have unsaved changes')
cy.contains('button', 'Cancel').click()
cy.contains('button', 'Save').click().wait('@savedViews')
cy.contains('button', 'Save').click().wait('@savedViews').wait(2000)
cy.contains('a', 'Dashboard').click()
cy.contains('You have unsaved changes').should('not.exist')
})
@@ -77,16 +86,16 @@ describe('settings', () => {
})
it('should remove saved view from sidebar when unset', () => {
cy.contains('a', 'Saved views').click()
cy.contains('a', 'Saved views').click().wait(2000)
cy.get('#show_in_sidebar_1').click()
cy.contains('button', 'Save').click().wait('@savedViews')
cy.contains('button', 'Save').click().wait('@savedViews').wait(2000)
cy.contains('li', 'Inbox').should('not.exist')
})
it('should remove saved view from dashboard when unset', () => {
cy.contains('a', 'Saved views').click()
cy.get('#show_on_dashboard_1').click()
cy.contains('button', 'Save').click().wait('@savedViews')
cy.contains('button', 'Save').click().wait('@savedViews').wait(2000)
cy.visit('/dashboard')
cy.get('app-saved-view-widget').contains('Inbox').should('not.exist')
})

View File

@@ -0,0 +1,27 @@
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "IMAP Server",
"imap_server": "imap.example.com",
"imap_port": 993,
"imap_security": 2,
"username": "inbox@example.com",
"password": "pass",
"character_set": "UTF-8"
},
{
"id": 2,
"name": "Gmail",
"imap_server": "imap.gmail.com",
"imap_port": 993,
"imap_security": 2,
"username": "user@gmail.com",
"password": "pass",
"character_set": "UTF-8"
}
]
}

View File

@@ -0,0 +1,29 @@
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Gmail",
"account": 2,
"folder": "INBOX",
"filter_from": null,
"filter_subject": "[paperless]",
"filter_body": null,
"filter_attachment_filename": null,
"maximum_age": 30,
"action": 3,
"action_parameter": null,
"assign_title_from": 1,
"assign_tags": [
9
],
"assign_correspondent_from": 1,
"assign_correspondent": 2,
"assign_document_type": null,
"order": 0,
"attachment_type": 2
}
]
}

View File

@@ -1 +1,44 @@
{"count":3,"next":null,"previous":null,"results":[{"id":1,"name":"Inbox","show_on_dashboard":true,"show_in_sidebar":true,"sort_field":"created","sort_reverse":true,"filter_rules":[{"rule_type":6,"value":"18"}]},{"id":2,"name":"Recently Added","show_on_dashboard":true,"show_in_sidebar":false,"sort_field":"created","sort_reverse":true,"filter_rules":[]},{"id":11,"name":"Taxes","show_on_dashboard":false,"show_in_sidebar":true,"sort_field":"created","sort_reverse":true,"filter_rules":[{"rule_type":6,"value":"39"}]}]}
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Inbox",
"show_on_dashboard": true,
"show_in_sidebar": true,
"sort_field": "created",
"sort_reverse": true,
"filter_rules": [
{
"rule_type": 6,
"value": "18"
}
]
},
{
"id": 2,
"name": "Recently Added",
"show_on_dashboard": true,
"show_in_sidebar": false,
"sort_field": "created",
"sort_reverse": true,
"filter_rules": []
},
{
"id": 11,
"name": "Taxes",
"show_on_dashboard": false,
"show_in_sidebar": true,
"sort_field": "created",
"sort_reverse": true,
"filter_rules": [
{
"rule_type": 6,
"value": "39"
}
]
}
]
}

File diff suppressed because it is too large Load Diff

3239
src-ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,17 +40,23 @@
"devDependencies": {
"@angular-builders/jest": "14.1.0",
"@angular-devkit/build-angular": "~14.2.7",
"@angular-eslint/builder": "14.4.0",
"@angular-eslint/eslint-plugin": "14.4.0",
"@angular-eslint/eslint-plugin-template": "14.4.0",
"@angular-eslint/schematics": "14.4.0",
"@angular-eslint/template-parser": "14.4.0",
"@angular/cli": "~14.2.7",
"@angular/compiler-cli": "~14.2.8",
"@types/jest": "28.1.6",
"@types/node": "^18.7.23",
"codelyzer": "^6.0.2",
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.43.0",
"concurrently": "7.4.0",
"eslint": "^8.28.0",
"jest": "28.1.3",
"jest-environment-jsdom": "^29.2.2",
"jest-preset-angular": "^12.2.3",
"ts-node": "~10.9.1",
"tslint": "~6.1.3",
"typescript": "~4.8.4",
"wait-on": "~6.0.1"
},

View File

@@ -47,6 +47,11 @@ const routes: Routes = [
component: SettingsComponent,
canDeactivate: [DirtyFormGuard],
},
{
path: 'settings/:section',
component: SettingsComponent,
canDeactivate: [DirtyFormGuard],
},
{ path: 'tasks', component: TasksComponent },
],
},

View File

@@ -191,21 +191,13 @@ export class AppComponent implements OnInit, OnDestroy {
},
{
anchorId: 'tour.settings',
content: $localize`Check out the settings for various tweaks to the web app or to toggle settings for saved views.`,
content: $localize`Check out the settings for various tweaks to the web app, toggle settings for saved views or setup e-mail checking.`,
route: '/settings',
enableBackdrop: true,
prevBtnTitle,
nextBtnTitle,
endBtnTitle,
},
{
anchorId: 'tour.admin',
content: $localize`The Admin area contains more advanced controls as well as the settings for automatic e-mail fetching.`,
enableBackdrop: true,
prevBtnTitle,
nextBtnTitle,
endBtnTitle,
},
{
anchorId: 'tour.outro',
title: $localize`Thank you! 🙏`,

View File

@@ -24,7 +24,7 @@ import { CorrespondentEditDialogComponent } from './components/common/edit-dialo
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { TagComponent } from './components/common/tag/tag.component'
import { ClearableBadge } from './components/common/clearable-badge/clearable-badge.component'
import { ClearableBadgeComponent } from './components/common/clearable-badge/clearable-badge.component'
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'
import { ToastsComponent } from './components/common/toasts/toasts.component'
@@ -39,6 +39,7 @@ import { NgxFileDropModule } from 'ngx-file-drop'
import { TextComponent } from './components/common/input/text/text.component'
import { SelectComponent } from './components/common/input/select/select.component'
import { CheckComponent } from './components/common/input/check/check.component'
import { PasswordComponent } from './components/common/input/password/password.component'
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
import { TagsComponent } from './components/common/input/tags/tags.component'
import { SortableDirective } from './directives/sortable.directive'
@@ -76,6 +77,8 @@ import { StoragePathEditDialogComponent } from './components/common/edit-dialog/
import { SettingsService } from './services/settings.service'
import { TasksComponent } from './components/manage/tasks/tasks.component'
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
import localeBe from '@angular/common/locales/be'
import localeCs from '@angular/common/locales/cs'
@@ -143,7 +146,7 @@ function initializeApp(settings: SettingsService) {
DocumentTypeEditDialogComponent,
StoragePathEditDialogComponent,
TagComponent,
ClearableBadge,
ClearableBadgeComponent,
PageHeaderComponent,
AppFrameComponent,
ToastsComponent,
@@ -157,6 +160,7 @@ function initializeApp(settings: SettingsService) {
TextComponent,
SelectComponent,
CheckComponent,
PasswordComponent,
SaveViewConfigDialogComponent,
TagsComponent,
SortableDirective,
@@ -180,6 +184,8 @@ function initializeApp(settings: SettingsService) {
DocumentAsnComponent,
DocumentCommentsComponent,
TasksComponent,
MailAccountEditDialogComponent,
MailRuleEditDialogComponent,
],
imports: [
BrowserModule,

View File

@@ -174,13 +174,6 @@
</svg><span>&nbsp;<ng-container i18n>Settings</ng-container></span>
</a>
</li>
<li class="nav-item" tourAnchor="tour.admin">
<a class="nav-link" href="admin/" ngbPopover="Admin" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#toggles"/>
</svg><span>&nbsp;<ng-container i18n>Admin</ng-container></span>
</a>
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">

View File

@@ -220,6 +220,12 @@ main {
font-size: 1rem;
}
@media screen and (min-width: 768px) {
.navbar-brand.slim {
max-width: 50px;
}
}
.dropdown.show .dropdown-toggle,
.dropdown-toggle:hover {
opacity: 0.7;

View File

@@ -5,7 +5,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'
templateUrl: './clearable-badge.component.html',
styleUrls: ['./clearable-badge.component.scss'],
})
export class ClearableBadge {
export class ClearableBadgeComponent {
constructor() {}
@Input()

View File

@@ -5,7 +5,6 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'
@Component({
selector: 'app-correspondent-edit-dialog',
@@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service'
styleUrls: ['./correspondent-edit-dialog.component.scss'],
})
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
constructor(
service: CorrespondentService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService)
constructor(service: CorrespondentService, activeModal: NgbActiveModal) {
super(service, activeModal)
}
getCreateTitle() {

View File

@@ -5,7 +5,6 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { ToastService } from 'src/app/services/toast.service'
@Component({
selector: 'app-document-type-edit-dialog',
@@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service'
styleUrls: ['./document-type-edit-dialog.component.scss'],
})
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
constructor(
service: DocumentTypeService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService)
constructor(service: DocumentTypeService, activeModal: NgbActiveModal) {
super(service, activeModal)
}
getCreateTitle() {

View File

@@ -2,11 +2,9 @@ import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
import { ObjectWithId } from 'src/app/data/object-with-id'
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
import { ToastService } from 'src/app/services/toast.service'
@Directive()
export abstract class EditDialogComponent<T extends ObjectWithId>
@@ -14,8 +12,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
{
constructor(
private service: AbstractPaperlessService<T>,
private activeModal: NgbActiveModal,
private toastService: ToastService
private activeModal: NgbActiveModal
) {}
@Input()
@@ -25,7 +22,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
object: T
@Output()
success = new EventEmitter()
succeeded = new EventEmitter()
networkActive = false
@@ -95,16 +92,16 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
break
}
this.networkActive = true
serverResponse.subscribe(
(result) => {
serverResponse.subscribe({
next: (result) => {
this.activeModal.close()
this.success.emit(result)
this.succeeded.emit(result)
},
(error) => {
error: (error) => {
this.error = error.error
this.networkActive = false
}
)
},
})
}
cancel() {

View File

@@ -0,0 +1,26 @@
<form [formGroup]="objectForm" (ngSubmit)="save()">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col">
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
<app-input-text i18n-title title="IMAP Server" formControlName="imap_server" [error]="error?.imap_server"></app-input-text>
<app-input-text i18n-title title="IMAP Port" formControlName="imap_port" [error]="error?.imap_port"></app-input-text>
<app-input-select i18n-title title="IMAP Security" [items]="imapSecurityOptions" formControlName="imap_security"></app-input-select>
</div>
<div class="col">
<app-input-text i18n-title title="Username" formControlName="username" [error]="error?.username"></app-input-text>
<app-input-password i18n-title title="Password" formControlName="password" [error]="error?.password"></app-input-password>
<app-input-text i18n-title title="Character Set" formControlName="character_set" [error]="error?.character_set"></app-input-text>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
</div>
</form>

View File

@@ -0,0 +1,50 @@
import { Component } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import {
IMAPSecurity,
PaperlessMailAccount,
} from 'src/app/data/paperless-mail-account'
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
const IMAP_SECURITY_OPTIONS = [
{ id: IMAPSecurity.None, name: $localize`No encryption` },
{ id: IMAPSecurity.SSL, name: $localize`SSL` },
{ id: IMAPSecurity.STARTTLS, name: $localize`STARTTLS` },
]
@Component({
selector: 'app-mail-account-edit-dialog',
templateUrl: './mail-account-edit-dialog.component.html',
styleUrls: ['./mail-account-edit-dialog.component.scss'],
})
export class MailAccountEditDialogComponent extends EditDialogComponent<PaperlessMailAccount> {
constructor(service: MailAccountService, activeModal: NgbActiveModal) {
super(service, activeModal)
}
getCreateTitle() {
return $localize`Create new mail account`
}
getEditTitle() {
return $localize`Edit mail account`
}
getForm(): FormGroup {
return new FormGroup({
name: new FormControl(null),
imap_server: new FormControl(null),
imap_port: new FormControl(null),
imap_security: new FormControl(IMAPSecurity.SSL),
username: new FormControl(null),
password: new FormControl(null),
character_set: new FormControl('UTF-8'),
})
}
get imapSecurityOptions() {
return IMAP_SECURITY_OPTIONS
}
}

View File

@@ -0,0 +1,39 @@
<form [formGroup]="objectForm" (ngSubmit)="save()">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col">
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
<app-input-select i18n-title title="Account" [items]="accounts" formControlName="account"></app-input-select>
<app-input-text i18n-title title="Folder" formControlName="folder" i18n-hint hint="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server." [error]="error?.folder"></app-input-text>
<app-input-number i18n-title title="Maximum age (days)" formControlName="maximum_age" [showAdd]="false" [error]="error?.maximum_age"></app-input-number>
<app-input-select i18n-title title="Attachment type" [items]="attachmentTypeOptions" formControlName="attachment_type"></app-input-select>
</div>
<div class="col">
<p class="small" i18n>Paperless will only process mails that match <em>all</em> of the filters specified below.</p>
<app-input-text i18n-title title="Filter from" formControlName="filter_from" [error]="error?.filter_from"></app-input-text>
<app-input-text i18n-title title="Filter subject" formControlName="filter_subject" [error]="error?.filter_subject"></app-input-text>
<app-input-text i18n-title title="Filter body" formControlName="filter_body" [error]="error?.filter_body"></app-input-text>
<app-input-text i18n-title title="Filter attachment filename" formControlName="filter_attachment_filename" i18n-hint hint="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_attachment_filename"></app-input-text>
</div>
<div class="col">
<app-input-select i18n-title title="Action" [items]="actionOptions" formControlName="action" i18n-hint hint="Action is only performed when documents are consumed from the mail. Mails without attachments remain entirely untouched."></app-input-select>
<app-input-text i18n-title title="Action parameter" *ngIf="showActionParamField" formControlName="action_parameter" [error]="error?.action_parameter"></app-input-text>
<app-input-select i18n-title title="Assign title from" [items]="metadataTitleOptions" formControlName="assign_title_from"></app-input-select>
<app-input-tags [allowCreate]="false" formControlName="assign_tags"></app-input-tags>
<app-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></app-input-select>
<app-input-select i18n-title title="Assign correspondent from" [items]="metadataCorrespondentOptions" formControlName="assign_correspondent_from"></app-input-select>
<app-input-select *ngIf="showCorrespondentField" i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></app-input-select>
</div>
</div>
</div>
<div class="modal-footer">
<span class="text-danger" *ngIf="error?.non_field_errors"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
</div>
</form>

View File

@@ -0,0 +1,180 @@
import { Component } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { first } from 'rxjs'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
import {
MailAction,
MailFilterAttachmentType,
MailMetadataCorrespondentOption,
MailMetadataTitleOption,
PaperlessMailRule,
} from 'src/app/data/paperless-mail-rule'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
const ATTACHMENT_TYPE_OPTIONS = [
{
id: MailFilterAttachmentType.Attachments,
name: $localize`Only process attachments.`,
},
{
id: MailFilterAttachmentType.Everything,
name: $localize`Process all files, including 'inline' attachments.`,
},
]
const ACTION_OPTIONS = [
{
id: MailAction.Delete,
name: $localize`Delete`,
},
{
id: MailAction.Move,
name: $localize`Move to specified folder`,
},
{
id: MailAction.MarkRead,
name: $localize`Mark as read, don't process read mails`,
},
{
id: MailAction.Flag,
name: $localize`Flag the mail, don't process flagged mails`,
},
{
id: MailAction.Tag,
name: $localize`Tag the mail with specified tag, don't process tagged mails`,
},
]
const METADATA_TITLE_OPTIONS = [
{
id: MailMetadataTitleOption.FromSubject,
name: $localize`Use subject as title`,
},
{
id: MailMetadataTitleOption.FromFilename,
name: $localize`Use attachment filename as title`,
},
]
const METADATA_CORRESPONDENT_OPTIONS = [
{
id: MailMetadataCorrespondentOption.FromNothing,
name: $localize`Do not assign a correspondent`,
},
{
id: MailMetadataCorrespondentOption.FromEmail,
name: $localize`Use mail address`,
},
{
id: MailMetadataCorrespondentOption.FromName,
name: $localize`Use name (or mail address if not available)`,
},
{
id: MailMetadataCorrespondentOption.FromCustom,
name: $localize`Use correspondent selected below`,
},
]
@Component({
selector: 'app-mail-rule-edit-dialog',
templateUrl: './mail-rule-edit-dialog.component.html',
styleUrls: ['./mail-rule-edit-dialog.component.scss'],
})
export class MailRuleEditDialogComponent extends EditDialogComponent<PaperlessMailRule> {
accounts: PaperlessMailAccount[]
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
constructor(
service: MailRuleService,
activeModal: NgbActiveModal,
accountService: MailAccountService,
correspondentService: CorrespondentService,
documentTypeService: DocumentTypeService
) {
super(service, activeModal)
accountService
.listAll()
.pipe(first())
.subscribe((result) => (this.accounts = result.results))
correspondentService
.listAll()
.pipe(first())
.subscribe((result) => (this.correspondents = result.results))
documentTypeService
.listAll()
.pipe(first())
.subscribe((result) => (this.documentTypes = result.results))
}
getCreateTitle() {
return $localize`Create new mail rule`
}
getEditTitle() {
return $localize`Edit mail rule`
}
getForm(): FormGroup {
return new FormGroup({
name: new FormControl(null),
account: new FormControl(null),
folder: new FormControl('INBOX'),
filter_from: new FormControl(null),
filter_subject: new FormControl(null),
filter_body: new FormControl(null),
filter_attachment_filename: new FormControl(null),
maximum_age: new FormControl(null),
attachment_type: new FormControl(MailFilterAttachmentType.Attachments),
action: new FormControl(MailAction.MarkRead),
action_parameter: new FormControl(null),
assign_title_from: new FormControl(MailMetadataTitleOption.FromSubject),
assign_tags: new FormControl([]),
assign_document_type: new FormControl(null),
assign_correspondent_from: new FormControl(
MailMetadataCorrespondentOption.FromNothing
),
assign_correspondent: new FormControl(null),
})
}
get showCorrespondentField(): boolean {
return (
this.objectForm?.get('assign_correspondent_from')?.value ==
MailMetadataCorrespondentOption.FromCustom
)
}
get showActionParamField(): boolean {
return (
this.objectForm?.get('action')?.value == MailAction.Move ||
this.objectForm?.get('action')?.value == MailAction.Tag
)
}
get attachmentTypeOptions() {
return ATTACHMENT_TYPE_OPTIONS
}
get actionOptions() {
return ACTION_OPTIONS
}
get metadataTitleOptions() {
return METADATA_TITLE_OPTIONS
}
get metadataCorrespondentOptions() {
return METADATA_CORRESPONDENT_OPTIONS
}
}

View File

@@ -6,7 +6,7 @@
</div>
<div class="modal-body">
<p *ngIf="this.dialogMode == 'edit'" i18n>
<p *ngIf="this.dialogMode === 'edit'" i18n>
<em>Note that editing a path does not apply changes to stored files until you have run the 'document_renamer' utility. See the <a target="_blank" href="https://docs.paperless-ngx.com/administration/#renamer">documentation</a>.</em>
</p>

View File

@@ -5,7 +5,6 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { ToastService } from 'src/app/services/toast.service'
@Component({
selector: 'app-storage-path-edit-dialog',
@@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service'
styleUrls: ['./storage-path-edit-dialog.component.scss'],
})
export class StoragePathEditDialogComponent extends EditDialogComponent<PaperlessStoragePath> {
constructor(
service: StoragePathService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService)
constructor(service: StoragePathService, activeModal: NgbActiveModal) {
super(service, activeModal)
}
get pathHint() {

View File

@@ -4,7 +4,6 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import { PaperlessTag } from 'src/app/data/paperless-tag'
import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'
import { randomColor } from 'src/app/utils/color'
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
@@ -14,12 +13,8 @@ import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
styleUrls: ['./tag-edit-dialog.component.scss'],
})
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
constructor(
service: TagService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService)
constructor(service: TagService, activeModal: NgbActiveModal) {
super(service, activeModal)
}
getCreateTitle() {

View File

@@ -4,7 +4,7 @@
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
</svg>
<div class="d-none d-sm-inline">&nbsp;{{title}}</div>
<ng-container *ngIf="!editing && selectionModel.selectionSize() > 0">
<ng-container *ngIf="!editing && selectionModel.totalCount > 0">
<app-clearable-badge [number]="multiple ? selectionModel.totalCount : undefined" [selected]="!multiple && selectionModel.selectionSize() > 0" (cleared)="reset()"></app-clearable-badge>
</ng-container>
</button>

View File

@@ -321,7 +321,7 @@ export class FilterableDropdownComponent {
apply = new EventEmitter<ChangedItems>()
@Output()
open = new EventEmitter()
opened = new EventEmitter()
get operatorToggleEnabled(): boolean {
return (
@@ -356,7 +356,7 @@ export class FilterableDropdownComponent {
if (this.editing) {
this.selectionModel.reset()
}
this.open.next(this)
this.opened.next(this)
} else {
this.filterText = ''
if (this.applyOnClose && this.selectionModel.isDirty()) {

View File

@@ -2,7 +2,7 @@
<label class="form-label" [for]="inputId">{{title}}</label>
<div class="input-group" [class.is-invalid]="error">
<input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
<button class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
<button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
</div>
<div class="invalid-feedback">
{{error}}

View File

@@ -1,4 +1,4 @@
import { Component, forwardRef } from '@angular/core'
import { Component, forwardRef, Input } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type'
import { DocumentService } from 'src/app/services/rest/document.service'
@@ -17,6 +17,9 @@ import { AbstractInputComponent } from '../abstract-input'
styleUrls: ['./number.component.scss'],
})
export class NumberComponent extends AbstractInputComponent<number> {
@Input()
showAdd: boolean = true
constructor(private documentService: DocumentService) {
super()
}

View File

@@ -0,0 +1,8 @@
<div class="mb-3">
<label class="form-label" [for]="inputId">{{title}}</label>
<input #inputField type="password" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
<div class="invalid-feedback">
{{error}}
</div>
</div>

View File

@@ -0,0 +1,21 @@
import { Component, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { AbstractInputComponent } from '../abstract-input'
@Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PasswordComponent),
multi: true,
},
],
selector: 'app-input-password',
templateUrl: './password.component.html',
styleUrls: ['./password.component.scss'],
})
export class PasswordComponent extends AbstractInputComponent<string> {
constructor() {
super()
}
}

View File

@@ -7,7 +7,7 @@
[closeOnSelect]="false"
[clearSearchOnAdd]="true"
[hideSelected]="true"
[addTag]="createTagRef"
[addTag]="allowCreate ? createTagRef : false"
addTagText="Add tag"
i18n-addTagText
(change)="onChange(value)"
@@ -31,7 +31,7 @@
</ng-template>
</ng-select>
<button class="btn btn-outline-secondary" type="button" (click)="createTag()">
<button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>

View File

@@ -54,6 +54,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
@Input()
suggestions: number[]
@Input()
allowCreate: boolean = true
value: number[]
tags: PaperlessTag[]

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ObjectWithId } from 'src/app/data/object-with-id'
@@ -7,7 +7,7 @@ import { ObjectWithId } from 'src/app/data/object-with-id'
templateUrl: './select-dialog.component.html',
styleUrls: ['./select-dialog.component.scss'],
})
export class SelectDialogComponent implements OnInit {
export class SelectDialogComponent {
constructor(public activeModal: NgbActiveModal) {}
@Output()
@@ -24,8 +24,6 @@ export class SelectDialogComponent implements OnInit {
selected: number
ngOnInit(): void {}
cancelClicked() {
this.activeModal.close()
}

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
import { PaperlessTag } from 'src/app/data/paperless-tag'
@Component({
@@ -6,7 +6,7 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
templateUrl: './tag.component.html',
styleUrls: ['./tag.component.scss'],
})
export class TagComponent implements OnInit {
export class TagComponent {
constructor() {}
@Input()
@@ -17,6 +17,4 @@ export class TagComponent implements OnInit {
@Input()
clickable: boolean = false
ngOnInit(): void {}
}

View File

@@ -11,9 +11,9 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let doc of documents" (click)="openDocumentsService.openDocument(doc)">
<td>{{doc.created_date | customDate}}</td>
<td>{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t); $event.stopPropagation();"></app-tag></td>
<tr *ngFor="let doc of documents">
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></app-tag></a></td>
</tr>
</tbody>
</table>

View File

@@ -7,6 +7,6 @@ th:first-child {
width: 25%;
}
tbody tr {
tbody app-tag {
cursor: pointer;
}

View File

@@ -72,7 +72,9 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
}
}
clickTag(tag: PaperlessTag) {
clickTag(tag: PaperlessTag, event: MouseEvent) {
event.preventDefault()
this.list.quickFilter([
{ rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
])

View File

@@ -1,6 +1,6 @@
<app-widget-frame title="Statistics" [loading]="loading" i18n-title>
<ng-container content>
<p class="card-text" i18n *ngIf="statistics?.documents_inbox != null">Documents in inbox: {{statistics?.documents_inbox}}</p>
<p class="card-text" i18n *ngIf="statistics?.documents_inbox !== null">Documents in inbox: {{statistics?.documents_inbox}}</p>
<p class="card-text" i18n>Total documents: {{statistics?.documents_total}}</p>
</ng-container>
</app-widget-frame>

View File

@@ -1,6 +1,5 @@
import { HttpEventType } from '@angular/common/http'
import { Component, OnInit } from '@angular/core'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import { Component } from '@angular/core'
import { NgxFileDropEntry } from 'ngx-file-drop'
import {
ConsumerStatusService,
FileStatus,
@@ -15,7 +14,7 @@ const MAX_ALERTS = 5
templateUrl: './upload-file-widget.component.html',
styleUrls: ['./upload-file-widget.component.scss'],
})
export class UploadFileWidgetComponent implements OnInit {
export class UploadFileWidgetComponent {
alertsExpanded = false
constructor(
@@ -109,8 +108,6 @@ export class UploadFileWidgetComponent implements OnInit {
this.consumerStatusService.dismissCompleted()
}
ngOnInit(): void {}
public fileOver(event) {}
public fileLeave(event) {}

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { Component } from '@angular/core'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@Component({
@@ -6,8 +6,6 @@ import { TourService } from 'ngx-ui-tour-ng-bootstrap'
templateUrl: './welcome-widget.component.html',
styleUrls: ['./welcome-widget.component.scss'],
})
export class WelcomeWidgetComponent implements OnInit {
export class WelcomeWidgetComponent {
constructor(public readonly tourService: TourService) {}
ngOnInit(): void {}
}

View File

@@ -1,11 +1,11 @@
import { Component, Input, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-widget-frame',
templateUrl: './widget-frame.component.html',
styleUrls: ['./widget-frame.component.scss'],
})
export class WidgetFrameComponent implements OnInit {
export class WidgetFrameComponent {
constructor() {}
@Input()
@@ -13,6 +13,4 @@ export class WidgetFrameComponent implements OnInit {
@Input()
loading: boolean = false
ngOnInit(): void {}
}

View File

@@ -1,5 +1,5 @@
<app-page-header [(title)]="title">
<div class="input-group input-group-sm me-5 d-none d-md-flex" *ngIf="getContentType() == 'application/pdf' && !useNativePdfViewer">
<div class="input-group input-group-sm me-5 d-none d-md-flex" *ngIf="getContentType() === 'application/pdf' && !useNativePdfViewer">
<div class="input-group-text" i18n>Page</div>
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages" [(ngModel)]="previewCurrentPage" />
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
@@ -149,9 +149,9 @@
<li [ngbNavItem]="4" class="d-md-none">
<a ngbNavLink>Preview</a>
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent == undefined">
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent === undefined">
<div class="position-relative">
<ng-container *ngIf="getContentType() == 'application/pdf'">
<ng-container *ngIf="getContentType() === 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
@@ -159,7 +159,7 @@
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<ng-container *ngIf="getContentType() === 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">
@@ -180,14 +180,14 @@
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>&nbsp;
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || (isDirty$ | async) === false">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || (isDirty$ | async) === false || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive || (isDirty$ | async) === false || error">Save</button>&nbsp;
</form>
</div>
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
<ng-container *ngIf="getContentType() == 'application/pdf'">
<ng-container *ngIf="getContentType() === 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
@@ -195,7 +195,7 @@
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<ng-container *ngIf="getContentType() === 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">

View File

@@ -184,7 +184,7 @@ export class DocumentDetailComponent
this.openDocumentService.getOpenDocument(this.documentId)
)
} else {
this.openDocumentService.openDocument(doc, false)
this.openDocumentService.openDocument(doc)
this.updateComponent(doc)
}

View File

@@ -1,11 +1,11 @@
import { Component, Input, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-metadata-collapse',
templateUrl: './metadata-collapse.component.html',
styleUrls: ['./metadata-collapse.component.scss'],
})
export class MetadataCollapseComponent implements OnInit {
export class MetadataCollapseComponent {
constructor() {}
expand = false
@@ -15,6 +15,4 @@ export class MetadataCollapseComponent implements OnInit {
@Input()
title = $localize`Metadata`
ngOnInit(): void {}
}

View File

@@ -66,7 +66,6 @@
</div>
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
<div class="btn-group btn-group-sm me-2">
<div ngbDropdown class="me-2 d-flex">
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
<svg class="toolbaricon" fill="currentColor">
@@ -75,26 +74,57 @@
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Actions</ng-container></div>
</button>
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
<button ngbDropdownItem [disabled]="awaitingDownload" (click)="downloadSelected()" i18n>
Download
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Preparing download...</span>
</div>
</button>
<button ngbDropdownItem [disabled]="awaitingDownload" (click)="downloadSelected('originals')" i18n>
Download originals
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Preparing download...</span>
</div>
</button>
<button ngbDropdownItem (click)="redoOcrSelected()" i18n>Redo OCR</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
<div class="btn-group btn-group-sm me-2">
<button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
<svg *ngIf="!awaitingDownload" class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#arrow-down" />
</svg>
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Preparing download...</span>
</div>
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Download</ng-container></div>
</button>
<div ngbDropdown class="me-2 d-flex btn-group" role="group">
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle-split rounded-end" ngbDropdownToggle></button>
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
<form [formGroup]="downloadForm" class="px-3 py-1">
<p class="mb-1" i18n>Include:</p>
<div class="form-group ps-3 mb-2">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="downloadFileType_archive" formControlName="downloadFileTypeArchive" />
<label class="form-check-label" for="downloadFileType_archive" i18n>
Archived files
</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="downloadFileType_originals" formControlName="downloadFileTypeOriginals" />
<label class="form-check-label" for="downloadFileType_originals" i18n>
Original files
</label>
</div>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="downloadUseFormatting" formControlName="downloadUseFormatting" />
<label class="form-check-label" for="downloadUseFormatting" i18n>
Use formatted filename
</label>
</div>
</form>
</div>
</div>
</div>
<div class="btn-group btn-group-sm me-2">
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,7 @@
.dropdown-toggle-split {
--bs-border-radius: .25rem;
}
.dropdown-menu{
--bs-dropdown-min-width: 12rem;
}

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { PaperlessTag } from 'src/app/data/paperless-tag'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
@@ -25,13 +25,15 @@ import { saveAs } from 'file-saver'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { FormControl, FormGroup } from '@angular/forms'
import { first, Subject, takeUntil } from 'rxjs'
@Component({
selector: 'app-bulk-editor',
templateUrl: './bulk-editor.component.html',
styleUrls: ['./bulk-editor.component.scss'],
})
export class BulkEditorComponent {
export class BulkEditorComponent implements OnInit, OnDestroy {
tags: PaperlessTag[]
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
@@ -43,6 +45,14 @@ export class BulkEditorComponent {
storagePathsSelectionModel = new FilterableDropdownSelectionModel()
awaitingDownload: boolean
unsubscribeNotifier: Subject<any> = new Subject()
downloadForm = new FormGroup({
downloadFileTypeArchive: new FormControl(true),
downloadFileTypeOriginals: new FormControl(false),
downloadUseFormatting: new FormControl(false),
})
constructor(
private documentTypeService: DocumentTypeService,
private tagService: TagService,
@@ -66,16 +76,46 @@ export class BulkEditorComponent {
ngOnInit() {
this.tagService
.listAll()
.pipe(first())
.subscribe((result) => (this.tags = result.results))
this.correspondentService
.listAll()
.pipe(first())
.subscribe((result) => (this.correspondents = result.results))
this.documentTypeService
.listAll()
.pipe(first())
.subscribe((result) => (this.documentTypes = result.results))
this.storagePathService
.listAll()
.pipe(first())
.subscribe((result) => (this.storagePaths = result.results))
this.downloadForm
.get('downloadFileTypeArchive')
.valueChanges.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newValue) => {
if (!newValue) {
this.downloadForm
.get('downloadFileTypeOriginals')
.patchValue(true, { emitEvent: false })
}
})
this.downloadForm
.get('downloadFileTypeOriginals')
.valueChanges.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newValue) => {
if (!newValue) {
this.downloadForm
.get('downloadFileTypeArchive')
.patchValue(true, { emitEvent: false })
}
})
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(this)
this.unsubscribeNotifier.complete()
}
private executeBulkOperation(modal, method: string, args) {
@@ -84,8 +124,9 @@ export class BulkEditorComponent {
}
this.documentService
.bulkEdit(Array.from(this.list.selected), method, args)
.subscribe(
(response) => {
.pipe(first())
.subscribe({
next: () => {
this.list.reload()
this.list.reduceSelectionToFilter()
this.list.selected.forEach((id) => {
@@ -95,7 +136,7 @@ export class BulkEditorComponent {
modal.close()
}
},
(error) => {
error: (error) => {
if (modal) {
modal.componentInstance.buttonsEnabled = true
}
@@ -104,8 +145,8 @@ export class BulkEditorComponent {
error.error
)}`
)
}
)
},
})
}
private applySelectionData(
@@ -126,6 +167,7 @@ export class BulkEditorComponent {
openTagsDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
.pipe(first())
.subscribe((s) => {
this.applySelectionData(s.selected_tags, this.tagSelectionModel)
})
@@ -134,6 +176,7 @@ export class BulkEditorComponent {
openDocumentTypeDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
.pipe(first())
.subscribe((s) => {
this.applySelectionData(
s.selected_document_types,
@@ -145,6 +188,7 @@ export class BulkEditorComponent {
openCorrespondentDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
.pipe(first())
.subscribe((s) => {
this.applySelectionData(
s.selected_correspondents,
@@ -156,6 +200,7 @@ export class BulkEditorComponent {
openStoragePathDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
.pipe(first())
.subscribe((s) => {
this.applySelectionData(
s.selected_storage_paths,
@@ -232,12 +277,14 @@ export class BulkEditorComponent {
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'modify_tags', {
add_tags: changedTags.itemsToAdd.map((t) => t.id),
remove_tags: changedTags.itemsToRemove.map((t) => t.id),
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.executeBulkOperation(modal, 'modify_tags', {
add_tags: changedTags.itemsToAdd.map((t) => t.id),
remove_tags: changedTags.itemsToRemove.map((t) => t.id),
})
})
})
} else {
this.executeBulkOperation(null, 'modify_tags', {
add_tags: changedTags.itemsToAdd.map((t) => t.id),
@@ -270,11 +317,13 @@ export class BulkEditorComponent {
}
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'set_correspondent', {
correspondent: correspondent ? correspondent.id : null,
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.executeBulkOperation(modal, 'set_correspondent', {
correspondent: correspondent ? correspondent.id : null,
})
})
})
} else {
this.executeBulkOperation(null, 'set_correspondent', {
correspondent: correspondent ? correspondent.id : null,
@@ -306,11 +355,13 @@ export class BulkEditorComponent {
}
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'set_document_type', {
document_type: documentType ? documentType.id : null,
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.executeBulkOperation(modal, 'set_document_type', {
document_type: documentType ? documentType.id : null,
})
})
})
} else {
this.executeBulkOperation(null, 'set_document_type', {
document_type: documentType ? documentType.id : null,
@@ -342,11 +393,13 @@ export class BulkEditorComponent {
}
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'set_storage_path', {
storage_path: storagePath ? storagePath.id : null,
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.executeBulkOperation(modal, 'set_storage_path', {
storage_path: storagePath ? storagePath.id : null,
})
})
})
} else {
this.executeBulkOperation(null, 'set_storage_path', {
storage_path: storagePath ? storagePath.id : null,
@@ -364,16 +417,30 @@ export class BulkEditorComponent {
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Delete document(s)`
modal.componentInstance.confirmClicked.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.executeBulkOperation(modal, 'delete', {})
})
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.executeBulkOperation(modal, 'delete', {})
})
}
downloadSelected(content = 'archive') {
downloadSelected() {
this.awaitingDownload = true
let downloadFileType: string =
this.downloadForm.get('downloadFileTypeArchive').value &&
this.downloadForm.get('downloadFileTypeOriginals').value
? 'both'
: this.downloadForm.get('downloadFileTypeArchive').value
? 'archive'
: 'originals'
this.documentService
.bulkDownload(Array.from(this.list.selected), content)
.bulkDownload(
Array.from(this.list.selected),
downloadFileType,
this.downloadForm.get('downloadUseFormatting').value
)
.pipe(first())
.subscribe((result: any) => {
saveAs(result, 'documents.zip')
this.awaitingDownload = false
@@ -389,9 +456,11 @@ export class BulkEditorComponent {
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Proceed`
modal.componentInstance.confirmClicked.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.executeBulkOperation(modal, 'redo_ocr', {})
})
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.executeBulkOperation(modal, 'redo_ocr', {})
})
}
}

View File

@@ -37,7 +37,7 @@
<use xlink:href="assets/bootstrap-icons.svg#diagram-3"/>
</svg>&nbsp;<span class="d-none d-md-inline" i18n>More like this</span>
</a>
<a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary">
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary">
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
<use xlink:href="assets/bootstrap-icons.svg#pencil"/>
</svg>&nbsp;<span class="d-none d-md-inline" i18n>Edit</span>

View File

@@ -2,7 +2,6 @@ import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core'
@@ -10,9 +9,6 @@ import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
@Component({
@@ -23,11 +19,10 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
'../popover-preview/popover-preview.scss',
],
})
export class DocumentCardLargeComponent implements OnInit {
export class DocumentCardLargeComponent {
constructor(
private documentService: DocumentService,
private settingsService: SettingsService,
public openDocumentsService: OpenDocumentsService
private settingsService: SettingsService
) {}
@Input()
@@ -75,8 +70,6 @@ export class DocumentCardLargeComponent implements OnInit {
}
}
ngOnInit(): void {}
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}
@@ -119,6 +112,9 @@ export class DocumentCardLargeComponent implements OnInit {
}
get contentTrimmed() {
return this.document.content.substr(0, 500)
return (
this.document.content.substr(0, 500) +
(this.document.content.length > 500 ? '...' : '')
)
}
}

View File

@@ -67,7 +67,7 @@
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group w-100">
<a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" title="Edit" i18n-title>
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Edit" i18n-title>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>

View File

@@ -2,7 +2,6 @@ import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core'
@@ -11,7 +10,6 @@ import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
@Component({
@@ -22,11 +20,10 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
'../popover-preview/popover-preview.scss',
],
})
export class DocumentCardSmallComponent implements OnInit {
export class DocumentCardSmallComponent {
constructor(
private documentService: DocumentService,
private settingsService: SettingsService,
public openDocumentsService: OpenDocumentsService
private settingsService: SettingsService
) {}
@Input()
@@ -57,8 +54,6 @@ export class DocumentCardSmallComponent implements OnInit {
mouseOnPreview = false
popoverHidden = true
ngOnInit(): void {}
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}

View File

@@ -53,7 +53,7 @@
</div>
<div>
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSortField(f.field)"
[class.active]="list.sortField == f.field">{{f.name}}
[class.active]="list.sortField === f.field">{{f.name}}
</button>
</div>
</div>
@@ -94,7 +94,7 @@
</ng-container>
<span i18n *ngIf="list.selected.size > 0">{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
<ng-container *ngIf="!list.isReloading">
<span i18n *ngIf="list.selected.size == 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>&nbsp;<span i18n *ngIf="isFiltered">(filtered)</span>
<span i18n *ngIf="list.selected.size === 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>&nbsp;<span i18n *ngIf="isFiltered">(filtered)</span>
</ng-container>
</p>
<ngb-pagination *ngIf="list.collectionSize" [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
@@ -111,52 +111,52 @@
</ng-container>
<ng-template #documentListNoError>
<div *ngIf="displayMode == 'largeCards'">
<div *ngIf="displayMode === 'largeCards'">
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
</app-document-card-large>
</div>
<table class="table table-sm align-middle border shadow-sm" *ngIf="displayMode == 'details'">
<table class="table table-sm align-middle border shadow-sm" *ngIf="displayMode === 'details'">
<thead>
<th></th>
<th class="d-none d-lg-table-cell"
sortable="archive_serial_number"
appSortable="archive_serial_number"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>ASN</th>
<th class="d-none d-md-table-cell"
sortable="correspondent__name"
appSortable="correspondent__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Correspondent</th>
<th
sortable="title"
appSortable="title"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Title</th>
<th class="d-none d-xl-table-cell"
sortable="document_type__name"
appSortable="document_type__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Document type</th>
<th class="d-none d-xl-table-cell"
sortable="storage_path__name"
appSortable="storage_path__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Storage path</th>
<th
sortable="created"
appSortable="created"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Created</th>
<th class="d-none d-xl-table-cell"
sortable="added"
appSortable="added"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
@@ -179,7 +179,7 @@
</ng-container>
</td>
<td>
<a (click)="openDocumentsService.openDocument(d)" 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>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
</td>
<td class="d-none d-xl-table-cell">
@@ -202,7 +202,7 @@
</tbody>
</table>
<div class="row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<div class="row row-cols-paperless-cards" *ngIf="displayMode === 'smallCards'">
<app-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickStoragePath)="clickStoragePath($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
</div>
<div *ngIf="list.documents?.length > 15" class="mt-3">

View File

@@ -5,10 +5,10 @@
<div ngbDropdown>
<button class="btn btn-sm btn-outline-primary" ngbDropdownToggle>{{textFilterTargetName}}</button>
<div class="dropdown-menu shadow" ngbDropdownMenu>
<button *ngFor="let t of textFilterTargets" ngbDropdownItem [class.active]="textFilterTarget == t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
<button *ngFor="let t of textFilterTargets" ngbDropdownItem [class.active]="textFilterTarget === t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
</div>
</div>
<select *ngIf="textFilterTarget == 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
<select *ngIf="textFilterTarget === 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
<option *ngFor="let m of textFilterModifiers" ngbDropdownItem [value]="m.id">{{m.label}}</option>
</select>
<button *ngIf="_textFilter" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0 z-10" (click)="resetTextField()">
@@ -16,7 +16,7 @@
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
</svg>
</button>
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget == 'fulltext-morelike'">
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget === 'fulltext-morelike'">
</div>
</div>
</div>

View File

@@ -16,10 +16,10 @@
<table class="table table-striped align-middle border shadow-sm">
<thead>
<tr>
<th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" class="d-none d-sm-table-cell" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
<th scope="col" *ngFor="let column of extraColumns" sortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
<th scope="col" appSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" class="d-none d-sm-table-cell" appSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" appSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
<th scope="col" *ngFor="let column of extraColumns" appSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>

View File

@@ -120,8 +120,20 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
backdrop: 'static',
})
activeModal.componentInstance.dialogMode = 'create'
activeModal.componentInstance.success.subscribe((o) => {
this.reloadData()
activeModal.componentInstance.success.subscribe({
next: () => {
this.reloadData()
this.toastService.showInfo(
$localize`Successfully created ${this.typeName}.`
)
},
error: (e) => {
this.toastService.showInfo(
$localize`Error occurred while creating ${
this.typeName
} : ${e.toString()}.`
)
},
})
}
@@ -131,8 +143,20 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
})
activeModal.componentInstance.object = object
activeModal.componentInstance.dialogMode = 'edit'
activeModal.componentInstance.success.subscribe((o) => {
this.reloadData()
activeModal.componentInstance.success.subscribe({
next: () => {
this.reloadData()
this.toastService.showInfo(
$localize`Successfully updated ${this.typeName}.`
)
},
error: (e) => {
this.toastService.showInfo(
$localize`Error occurred while saving ${
this.typeName
} : ${e.toString()}.`
)
},
})
}

View File

@@ -1,12 +1,17 @@
<app-page-header title="Settings" i18n-title>
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
<a class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
<ng-container i18n>Open Django Admin</ng-container>
<svg class="sidebaricon ms-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#arrow-up-right"/>
</svg>
</a>
</app-page-header>
<!-- <p>items per page, documents per view type</p> -->
<form [formGroup]="settingsForm" (ngSubmit)="saveSettings()">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li [ngbNavItem]="1">
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [(activeId)]="activeNavID" class="nav-tabs">
<li [ngbNavItem]="SettingsNavIDs.General">
<a ngbNavLink i18n>General</a>
<ng-template ngbNavContent>
@@ -19,7 +24,7 @@
<div class="col">
<select class="form-select" formControlName="displayLanguage">
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale != 'en-US'"> - {{lang.englishName}}</span></option>
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale !== 'en-US'"> - {{lang.englishName}}</span></option>
</select>
<small *ngIf="displayLanguageIsDirty" class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
@@ -162,7 +167,7 @@
</ng-template>
</li>
<li [ngbNavItem]="2">
<li [ngbNavItem]="SettingsNavIDs.Notifications">
<a ngbNavLink i18n>Notifications</a>
<ng-template ngbNavContent>
@@ -180,7 +185,7 @@
</ng-template>
</li>
<li [ngbNavItem]="3">
<li [ngbNavItem]="SettingsNavIDs.SavedViews" (mouseover)="maybeInitializeTab(SettingsNavIDs.SavedViews)" (focusin)="maybeInitializeTab(SettingsNavIDs.SavedViews)">
<a ngbNavLink i18n>Saved views</a>
<ng-template ngbNavContent>
@@ -210,8 +215,97 @@
</div>
</div>
<div *ngIf="savedViews.length == 0" i18n>No saved views defined.</div>
<div *ngIf="savedViews && savedViews.length === 0" i18n>No saved views defined.</div>
<div *ngIf="!savedViews">
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="SettingsNavIDs.Mail" (mouseover)="maybeInitializeTab(SettingsNavIDs.Mail)" (focusin)="maybeInitializeTab(SettingsNavIDs.Mail)">
<a ngbNavLink i18n>Mail</a>
<ng-template ngbNavContent>
<ng-container *ngIf="mailAccounts && mailRules">
<h4>
<ng-container i18n>Mail accounts</ng-container>
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailAccount()">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add Account</ng-container>
</button>
</h4>
<ul class="list-group" formGroupName="mailAccounts">
<li class="list-group-item">
<div class="row">
<div class="col" i18n>Name</div>
<div class="col" i18n>Server</div>
<div class="col" i18n>Actions</div>
</div>
</li>
<li *ngFor="let account of mailAccounts" class="list-group-item" [formGroupName]="account.id">
<div class="row">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)">{{account.name}}</button></div>
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
<div class="col">
<div class="btn-group">
<button class="btn btn-sm btn-primary" type="button" (click)="editMailAccount(account)" i18n>Edit</button>
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)" i18n>Delete</button>
</div>
</div>
</div>
</li>
<div *ngIf="mailAccounts.length === 0" i18n>No mail accounts defined.</div>
</ul>
<h4 class="mt-4">
<ng-container i18n>Mail rules</ng-container>
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailRule()">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add Rule</ng-container>
</button>
</h4>
<ul class="list-group" formGroupName="mailRules">
<li class="list-group-item">
<div class="row">
<div class="col" i18n>Name</div>
<div class="col" i18n>Account</div>
<div class="col" i18n>Actions</div>
</div>
</li>
<li *ngFor="let rule of mailRules" class="list-group-item" [formGroupName]="rule.id">
<div class="row">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)">{{rule.name}}</button></div>
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
<div class="col">
<div class="btn-group">
<button class="btn btn-sm btn-primary" type="button" (click)="editMailRule(rule)" i18n>Edit</button>
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)" i18n>Delete</button>
</div>
</div>
</div>
</li>
<div *ngIf="mailRules.length === 0" i18n>No mail rules defined.</div>
</ul>
</ng-container>
<div *ngIf="!mailAccounts || !mailRules">
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
</div>
</ng-template>
@@ -220,5 +314,5 @@
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<button type="submit" class="btn btn-primary mb-2" [disabled]="!(isDirty$ | async)" i18n>Save</button>
<button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
</form>

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