Compare commits

..

493 Commits

Author SHA1 Message Date
jonaswinkler
a3c4e4c6b6 install script: configure time zone 2021-04-06 21:46:18 +02:00
jonaswinkler
645297d68c documentation 2021-04-06 21:46:16 +02:00
Jonas Winkler
6d8782f771 Merge pull request #860 from shamoon/feature/issue-858
Suppport search term PDF open parameter
2021-04-06 20:28:53 +02:00
Jonas Winkler
2728183eee New Crowdin updates (#871) 2021-04-06 20:23:18 +02:00
Michael Shamoon
3d0891c73b Updated to work with new unified search interface 2021-04-05 16:35:15 -07:00
Michael Shamoon
8226a8793e Merge remote-tracking branch 'upstream/dev' into feature/issue-858 2021-04-05 13:59:21 -07:00
jonaswinkler
d01477b337 changelog 2021-04-05 22:23:11 +02:00
jonaswinkler
6bb07c72ab API search documentation 2021-04-05 22:19:42 +02:00
jonaswinkler
a009462b80 update messages 2021-04-05 22:05:26 +02:00
jonaswinkler
9111b130e4 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-04-05 21:53:39 +02:00
jonaswinkler
1dbd7b9bb4 fix some issues with the search index 2021-04-05 21:53:07 +02:00
Jonas Winkler
2f95dfc0e7 New Crowdin updates (#855) 2021-04-05 21:44:14 +02:00
Jonas Winkler
acc317ddfd Merge pull request #818 from shamoon/fix/issue-817
Support passing current term from tag / document type / correspondent search field to 'create new' dialog
2021-04-05 21:43:50 +02:00
jonaswinkler
1fbd3935ea focus text filter input automatically 2021-04-05 21:43:20 +02:00
jonaswinkler
ca2bf962e9 force update of search index if out of date 2021-04-05 21:33:17 +02:00
jonaswinkler
1d27a3a14b fixed the write permission check 2021-04-05 21:33:04 +02:00
jonaswinkler
8e1a9dde05 bugfix 2021-04-05 21:05:32 +02:00
Michael Shamoon
8eabf8c77a Refactor unneeded ngIf 2021-04-04 19:57:16 -07:00
Michael Shamoon
ec4ec41552 Add back plus button which retains filter text 2021-04-04 17:05:27 -07:00
jonaswinkler
8960b0300f changelog and version bump 2021-04-05 00:36:44 +02:00
jonaswinkler
d6a2672cab Merge branch 'feature-unified-search' into dev 2021-04-05 00:25:10 +02:00
jonaswinkler
4b281ca89d logging before executing pre and post consume scripts 2021-04-05 00:22:11 +02:00
jonaswinkler
808b507b0f fix migration 2021-04-05 00:16:50 +02:00
jonaswinkler
ab47d03e1a update messages 2021-04-04 20:46:25 +02:00
jonaswinkler
aca999090b Merge branch 'dev' into feature-unified-search 2021-04-04 20:41:29 +02:00
jonaswinkler
1322aaf4da add migration to fix null characters in document contents 2021-04-04 20:41:08 +02:00
jonaswinkler
c49471fb3b bugfix 2021-04-04 01:25:54 +02:00
jonaswinkler
d13baab0a6 more testing 2021-04-04 01:19:07 +02:00
jonaswinkler
359b46c15b fixed the test cases 2021-04-04 00:29:40 +02:00
jonaswinkler
3b83e9a43d pycodestyle 2021-04-04 00:04:00 +02:00
jonaswinkler
ab7a499e8f refactor filter reset 2021-04-04 00:03:51 +02:00
jonaswinkler
fffe4f694f reset page when doing full text search 2021-04-03 22:19:12 +02:00
Michael Shamoon
18c028cafd Suppport search term PDF open parameter 2021-04-03 13:10:39 -07:00
jonaswinkler
4e289c7dab rename search 2021-04-03 21:56:33 +02:00
jonaswinkler
3dfe5c9262 fix page out of range with full text 2021-04-03 21:50:23 +02:00
jonaswinkler
be87caf7f6 fix search scores 2021-04-03 21:50:05 +02:00
jonaswinkler
b7063b199a disable sorting for now 2021-04-03 21:49:31 +02:00
jonaswinkler
1ed9c245f5 fixed more like this 2021-04-03 21:07:36 +02:00
jonaswinkler
fb1e9fe66a error messages for invalid search queries 2021-04-03 21:02:33 +02:00
jonaswinkler
38a386d5ae fix date filtering for full text search 2021-04-03 21:02:13 +02:00
jonaswinkler
726114575e Merge branch 'dev' into feature-unified-search 2021-04-03 20:31:16 +02:00
Michael Shamoon
026c213ea4 Refactor to use ng-select addTag function 2021-04-03 09:30:29 -07:00
jonaswinkler
cd85d4e86a update tooltips and messages 2021-04-02 14:59:02 +02:00
jonaswinkler
e906bf58f0 Save list view state across sessions 2021-04-02 14:46:45 +02:00
jonaswinkler
87d2209e6d update dependencies 2021-04-02 14:26:20 +02:00
jonaswinkler
704f8ea680 update dependencies 2021-04-02 14:21:05 +02:00
Jonas Winkler
1e101fb3db Merge pull request #842 from Bart1909/master
Implements #807
2021-04-02 14:08:01 +02:00
Jonas Winkler
a16643ee29 New Crowdin updates (#847) 2021-04-02 10:47:11 +02:00
Tobi
b783e0e211 refactoring 2021-03-30 20:51:49 +02:00
Tobi
1ec4570c8d renames document-asn component 2021-03-30 19:26:20 +02:00
Tobi
434194d290 Merge remote-tracking branch 'origin/master' 2021-03-30 19:07:44 +02:00
Tobi
f62e64357b implements #807 2021-03-30 19:07:29 +02:00
Jonas Winkler
b9f49c5eca Merge pull request #834 from benjaminfrank/patch-1
New android scanner app recommendation
2021-03-30 00:42:28 +02:00
Jonas Winkler
13a839de57 New Crowdin updates (#837) 2021-03-30 00:42:06 +02:00
benjaminfrank
7eb4253c00 Update scanners.rst 2021-03-27 14:40:13 +01:00
Jonas Winkler
dab210a183 Merge pull request #819 from shamoon/fix/issue-293-2
Remove all rem units on SVG elements
2021-03-26 23:13:06 +01:00
Jonas Winkler
5319d4782a New translations django.po (Italian) (#813) 2021-03-26 23:12:36 +01:00
Michael Shamoon
0463c3fe54 1.3em not 1.5em 2021-03-24 16:21:08 -07:00
Michael Shamoon
fe84430679 Remove all rem units on SVG elements
See https://bugzilla.mozilla.org/show_bug.cgi?id=1231147
2021-03-24 16:17:40 -07:00
Michael Shamoon
9514e79bfb Not needed, clearing will be done by timeout 2021-03-24 12:42:14 -07:00
Michael Shamoon
b6756595d9 Clear last search term when clear button clicked 2021-03-24 12:37:26 -07:00
Michael Shamoon
8ddb3e80b7 Add timeout for clearing last search term on select blur 2021-03-24 12:21:51 -07:00
Michael Shamoon
52bc1a62e1 Support passing current term from input-select search to create dialog e.g. for doc type / correspondent 2021-03-24 12:21:13 -07:00
Michael Shamoon
cd72ed2cec Support passing current term from tag search to create dialog 2021-03-24 12:13:37 -07:00
Jonas Winkler
ccaee4ce62 Update README.md 2021-03-24 10:31:05 +01:00
jonaswinkler
0e596bd1fc also apply \0 removal to sidecar contents 2021-03-22 23:08:34 +01:00
Jonas Winkler
21fafd3255 Update README.md 2021-03-22 23:03:01 +01:00
jonaswinkler
fda2bfbea7 better exception logging 2021-03-22 23:00:15 +01:00
jonaswinkler
d26c46e034 fixes #794 2021-03-22 22:46:35 +01:00
jonaswinkler
27cb243a2f fix dependencies 2021-03-21 13:50:48 +01:00
jonaswinkler
d3514fc5f9 update dependencies 2021-03-21 12:20:10 +01:00
jonaswinkler
4954851b68 Revert "update requirements"
This reverts commit 8f6e1360
2021-03-21 11:43:16 +01:00
Jonas Winkler
6bcb12ddc9 Merge pull request #796 from holzhannes/patch-2
Three new scanners, one iOS App and column for SMTP Support
2021-03-20 12:15:33 +01:00
HolzHannes
126a4ec21f Update scanners.rst
Some new scanners and a new Support column SMTP for Scanner which can directly send mails via SMTP. Also one iOS App to scan documents
2021-03-20 11:10:05 +01:00
Jonas Winkler
0aa084c792 New Crowdin updates (#790) 2021-03-18 23:50:54 +01:00
jonaswinkler
8f6e1360e1 update requirements 2021-03-18 23:49:49 +01:00
jonaswinkler
b7e570aba0 Merge branch 'dev' 2021-03-18 19:41:45 +01:00
jonaswinkler
ce2bae12af changelog and version bump 2021-03-18 19:13:45 +01:00
Jonas Winkler
b6111d8da6 New Crowdin updates (#788) 2021-03-18 16:22:45 +01:00
jonaswinkler
2fb1132b69 fix test case 2021-03-17 23:08:19 +01:00
jonaswinkler
9a04bc1beb fixes #771 2021-03-17 22:57:37 +01:00
jonaswinkler
b39c3f7866 fixes #668 (see https://github.com/the-paperless-project/paperless/pull/571) 2021-03-17 22:44:18 +01:00
jonaswinkler
740237a8fa add migration 2021-03-17 22:33:00 +01:00
jonaswinkler
391db73ea8 added pt-pt locale 2021-03-17 22:32:39 +01:00
jonaswinkler
b6ff88645b lots of changes for the new unified search 2021-03-17 22:25:22 +01:00
Jonas Winkler
4cd1672094 New Crowdin updates (#787) 2021-03-17 02:14:43 +01:00
jonaswinkler
630cd814e2 Merge branch 'dev' into feature-unified-search 2021-03-16 20:48:05 +01:00
jonaswinkler
6606d7572c API refactoring 2021-03-16 20:47:45 +01:00
Jonas Winkler
b331e3388f Merge pull request #778 from shamoon/fix/issue-774-775
Fix extra scrollbar on log container, auto scroll logs to bottom
2021-03-16 17:29:07 +01:00
Jonas Winkler
b6428aa85f Merge pull request #785 from isaacsando/master
update advanced_usage.rst to remove wording regarding filename parsing
2021-03-16 17:28:29 +01:00
isaacsando
fa6d554d1f update advanced_usage.rst to remove wording regarding filename parsing 2021-03-16 11:16:48 -05:00
Michael Shamoon
816871eed3 Redundant call to scroll 2021-03-16 03:08:48 -07:00
Michael Shamoon
ee04a9226b Don’t remove OnInit 2021-03-16 03:06:20 -07:00
Jonas Winkler
6b15093c43 New Crowdin updates (#776) 2021-03-16 10:40:38 +01:00
Michael Shamoon
b9d0954924 Automatically scroll log ouput 2021-03-15 09:56:17 -07:00
Michael Shamoon
a94a81d839 Revert "Reverse log order"
This reverts commit 2e21fbf619.
2021-03-15 08:45:33 -07:00
Michael Shamoon
46be3924e4 Revert "Side effecting"
This reverts commit 316a2469b8.
2021-03-15 08:45:30 -07:00
Michael Shamoon
316a2469b8 Side effecting 2021-03-15 08:42:10 -07:00
Michael Shamoon
2e21fbf619 Reverse log order 2021-03-15 08:38:55 -07:00
Michael Shamoon
e1254a053a Fix log container taller than viewport 2021-03-15 08:38:47 -07:00
Jonas Winkler
50bcd3daf1 New Crowdin updates (#764) 2021-03-15 10:48:25 +01:00
Jonas Winkler
7725973b62 Merge pull request #766 from shamoon/fix/additional-card-info-icons-fix
Fix additional card info icons sometimes cut off in certain browsers
2021-03-14 18:44:48 +01:00
Michael Shamoon
30bf7418f5 Dont change size overall 2021-03-14 09:35:53 -07:00
Michael Shamoon
eb611b2c41 Fix icons sometimes cut off in certain browsers 2021-03-14 09:33:57 -07:00
jonaswinkler
e4909c3133 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-14 15:22:34 +01:00
jonaswinkler
2216330118 bump version 2021-03-14 15:22:25 +01:00
Jonas Winkler
ca090aa2fa New Crowdin updates (#763) 2021-03-14 15:12:10 +01:00
jonaswinkler
ec7ca2c352 changelog 2021-03-14 14:49:38 +01:00
jonaswinkler
2eb5e289ce optimized default thumbnail 2021-03-14 14:45:29 +01:00
jonaswinkler
40ce38254b fixes #631 2021-03-14 14:42:48 +01:00
jonaswinkler
0ad2b05455 fixes #745 2021-03-14 14:17:21 +01:00
jonaswinkler
c74d261f6a Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-14 14:06:27 +01:00
Jonas Winkler
68fe18fe36 Merge pull request #748 from shamoon/feature/additional-card-info
Additional / organized card info
2021-03-14 14:06:08 +01:00
jonaswinkler
47313db2c7 changelog 2021-03-14 14:01:43 +01:00
jonaswinkler
127129fbeb Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-14 13:34:31 +01:00
jonaswinkler
f43a8d6f2e Add spanish locale 2021-03-14 13:34:14 +01:00
Jonas Winkler
7cde850d98 New Crowdin updates (#757)
* New translations django.po (Spanish)
[ci skip]

* New translations messages.xlf (Spanish)
[ci skip]
2021-03-14 13:28:12 +01:00
Jonas Winkler
1bcfc5b54d New Crowdin updates (#752) 2021-03-13 23:19:03 +01:00
Michael Shamoon
7a8494da4d Small card tooltip tweaks, medium date 2021-03-12 20:15:18 -08:00
Michael Shamoon
65733863b1 tooltip placement 2021-03-12 20:12:05 -08:00
Michael Shamoon
e62ecefcd7 dark mode background color 2021-03-12 20:12:00 -08:00
Michael Shamoon
0aacebf783 Display date & ASN on one line if fits 2021-03-12 20:00:23 -08:00
Michael Shamoon
43b1700e91 hover color for card footer buttons 2021-03-12 19:59:45 -08:00
Michael Shamoon
9b4625b4a5 Fix pager color in dark mode 2021-03-12 19:52:31 -08:00
Michael Shamoon
27998df6e4 div instead of li 2021-03-12 19:35:25 -08:00
Michael Shamoon
eb5eabf3c6 fix text not visible in light mode on large card 2021-03-12 09:17:49 -08:00
Michael Shamoon
76cee78408 Visual tweaks for responsive 2021-03-12 08:12:36 -08:00
Michael Shamoon
0ba35a6eb5 More semantically correct elements 2021-03-12 07:58:37 -08:00
Michael Shamoon
6a58bd30ad Fix incomplete doc type clicking for large cards 2021-03-12 07:21:00 -08:00
Michael Shamoon
e71d055ef2 Tooltip consistency 2021-03-12 07:19:14 -08:00
Michael Shamoon
cb2eff47cc Refactor 2021-03-12 07:15:00 -08:00
Michael Shamoon
4696f9896b Support large cards 2021-03-12 06:54:22 -08:00
Jonas Winkler
424835880d New Crowdin updates (#738) 2021-03-12 15:53:37 +01:00
Jonas Winkler
e0e0ee4a77 Merge pull request #744 from shamoon/fix/miscellaneous-css-fixes
Fixes for mobile logger cut off & dark mode hidden "logged in as"
2021-03-12 15:48:16 +01:00
Michael Shamoon
5eca0ce554 Light mode fixes 2021-03-12 06:30:03 -08:00
Michael Shamoon
f5a06ac0dd Slightly shorter thumbnail 2021-03-12 06:11:53 -08:00
Michael Shamoon
81ea8873e0 Use medium date 2021-03-12 06:02:39 -08:00
Michael Shamoon
63b4ccacde Fix log lines hidden on narrow screens 2021-03-11 16:03:17 -08:00
Michael Shamoon
ccc39e0e55 Fix illegible logged in as in dark mode 2021-03-11 16:03:00 -08:00
Michael Shamoon
d264df1504 Additional metadata for small cards 2021-03-11 15:54:30 -08:00
jonaswinkler
9b7bc16b3e keep the ng2-pdf-viewer; this might come in handy for splitting documents at specific pages #706 2021-03-10 22:38:33 +01:00
jonaswinkler
f7f51f4f73 Merge branch 'dev' into feature/popover-previews 2021-03-10 22:32:37 +01:00
jonaswinkler
143ae1092c changelog 2021-03-10 22:31:49 +01:00
jonaswinkler
6470a443ac fix #735 2021-03-10 18:32:57 +01:00
jonaswinkler
7795baf989 change ISO date to always display 4 digit years #736 2021-03-10 18:15:23 +01:00
Jonas Winkler
5ab717c6be New Crowdin updates (#727) 2021-03-10 13:55:03 +01:00
Jonas Winkler
fac265486a Merge pull request #726 from darmiel/feature/664-filter-by-asn
Implemented 'Filter by: ASN'
2021-03-08 21:00:58 +01:00
darmiel
707efff644 Updated messages.xlf 2021-03-08 20:40:18 +01:00
Jonas Winkler
15291fd659 New Crowdin updates (#725) 2021-03-08 19:41:25 +01:00
darmiel
6535a20b21 Implemented 'Filter by: ASN' 2021-03-08 17:11:25 +01:00
Jonas Winkler
8c922dbffc Merge pull request #715 from rost314/fix_nfs_write_check
fix writable check for NFS mounts
2021-03-08 12:34:37 +01:00
Jonas Winkler
874f5fd2ca New Crowdin updates (#722) 2021-03-08 12:31:37 +01:00
Jonas Winkler
50864b10dd New Crowdin updates (#719)
New translations
2021-03-08 10:04:03 +01:00
Robert
57a5df1fce fix writable check for NFS mounts
For some reason, os.access(directory, os.W_OK | os.X_OK) does not work correctly on NFS mounts.
After some research, found out that the only secure and portable way to check for write access, is to touch a file.
2021-03-07 23:46:29 +01:00
Jonas Winkler
14828b15d8 New Crowdin updates (#718)
* New translations messages.xlf (English, United Kingdom)
[ci skip]

* New translations django.po (Romanian)
[ci skip]

* New translations messages.xlf (Dutch)
[ci skip]

* New translations django.po (Xhosa)
[ci skip]

* New translations messages.xlf (English, United Kingdom)
[ci skip]

* New translations django.po (English, United Kingdom)
[ci skip]

* New translations messages.xlf (Portuguese, Brazilian)
[ci skip]

* New translations django.po (Portuguese, Brazilian)
[ci skip]

* New translations messages.xlf (Chinese Simplified)
[ci skip]

* New translations django.po (Chinese Simplified)
[ci skip]

* New translations messages.xlf (Russian)
[ci skip]

* New translations django.po (Russian)
[ci skip]

* New translations messages.xlf (Portuguese)
[ci skip]

* New translations django.po (Portuguese)
[ci skip]

* New translations django.po (Dutch)
[ci skip]

* New translations messages.xlf (Romanian)
[ci skip]

* New translations messages.xlf (Italian)
[ci skip]

* New translations django.po (Italian)
[ci skip]

* New translations messages.xlf (Hungarian)
[ci skip]

* New translations django.po (Hungarian)
[ci skip]

* New translations messages.xlf (German)
[ci skip]

* New translations django.po (German)
[ci skip]

* New translations messages.xlf (Czech)
[ci skip]

* New translations django.po (Czech)
[ci skip]

* New translations messages.xlf (Spanish)
[ci skip]

* New translations django.po (Spanish)
[ci skip]

* New translations messages.xlf (French)
[ci skip]

* New translations django.po (French)
[ci skip]

* New translations messages.xlf (Xhosa)
[ci skip]

* New translations django.po (Romanian)
[ci skip]

* New translations messages.xlf (Dutch)
[ci skip]

* New translations messages.xlf (English, United Kingdom)
[ci skip]

* New translations django.po (English, United Kingdom)
[ci skip]

* New translations messages.xlf (Portuguese, Brazilian)
[ci skip]

* New translations django.po (Portuguese, Brazilian)
[ci skip]

* New translations messages.xlf (Russian)
[ci skip]

* New translations django.po (Russian)
[ci skip]

* New translations django.po (Dutch)
[ci skip]

* New translations messages.xlf (Romanian)
[ci skip]

* New translations messages.xlf (Italian)
[ci skip]

* New translations django.po (Italian)
[ci skip]

* New translations messages.xlf (German)
[ci skip]

* New translations django.po (German)
[ci skip]

* New translations messages.xlf (Spanish)
[ci skip]

* New translations django.po (Spanish)
[ci skip]

* New translations messages.xlf (French)
[ci skip]

* New translations django.po (French)
[ci skip]
2021-03-07 22:37:04 +01:00
jonaswinkler
5f03e9e4cc Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-07 21:35:04 +01:00
jonaswinkler
c2b82fa063 fix up some messages 2021-03-07 21:34:55 +01:00
Jonas Winkler
40c4cfda75 Merge pull request #713 from jonaswinkler/l10n_dev
New Crowdin updates
2021-03-07 18:19:17 +01:00
Jonas Winkler
5c5ce6eedb New translations messages.xlf (Italian)
[ci skip]
2021-03-07 14:09:28 +01:00
jonaswinkler
f9263ddb62 some initial attempts to merge search and document list 2021-03-07 13:16:23 +01:00
Jonas Winkler
2fd1074729 New translations messages.xlf (Dutch)
[ci skip]
2021-03-07 13:11:07 +01:00
Jonas Winkler
2e379ad422 Merge pull request #712 from jonaswinkler/l10n_dev
New Crowdin updates
2021-03-07 12:27:43 +01:00
Jonas Winkler
c1cecb3e0e New translations messages.xlf (Dutch)
[ci skip]
2021-03-07 11:08:33 +01:00
Jonas Winkler
b0d13bed72 New translations messages.xlf (German)
[ci skip]
2021-03-07 11:08:32 +01:00
Jonas Winkler
d23a0d230c New translations django.po (German)
[ci skip]
2021-03-07 11:08:30 +01:00
Jonas Winkler
91b87f4e2c Merge pull request #711 from jonaswinkler/l10n_dev
New Crowdin updates
2021-03-07 11:06:08 +01:00
Jonas Winkler
a77c1f3ad1 New translations messages.xlf (Xhosa)
[ci skip]
2021-03-07 10:14:00 +01:00
Jonas Winkler
4c3cdaab0e New translations messages.xlf (French)
[ci skip]
2021-03-07 10:13:57 +01:00
Jonas Winkler
3e86c3d0d0 New translations messages.xlf (Spanish)
[ci skip]
2021-03-07 10:13:55 +01:00
Jonas Winkler
5a40d3c6fe New translations messages.xlf (Czech)
[ci skip]
2021-03-07 10:13:52 +01:00
Jonas Winkler
f79c1e0bcc New translations messages.xlf (German)
[ci skip]
2021-03-07 10:13:50 +01:00
Jonas Winkler
8f14ff7210 New translations messages.xlf (Hungarian)
[ci skip]
2021-03-07 10:13:48 +01:00
Jonas Winkler
90732d6a2f New translations messages.xlf (Italian)
[ci skip]
2021-03-07 10:13:46 +01:00
Jonas Winkler
f179ff9da4 New translations messages.xlf (Romanian)
[ci skip]
2021-03-07 10:13:44 +01:00
Jonas Winkler
90fd999220 New translations messages.xlf (Dutch)
[ci skip]
2021-03-07 10:13:43 +01:00
Jonas Winkler
04afbdd938 New translations messages.xlf (Portuguese)
[ci skip]
2021-03-07 10:13:41 +01:00
Jonas Winkler
5d3ef3d338 New translations messages.xlf (Russian)
[ci skip]
2021-03-07 10:13:39 +01:00
Jonas Winkler
4477d083e8 New translations messages.xlf (Chinese Simplified)
[ci skip]
2021-03-07 10:13:37 +01:00
Jonas Winkler
b7b3f54617 New translations messages.xlf (Portuguese, Brazilian)
[ci skip]
2021-03-07 10:13:35 +01:00
Jonas Winkler
5fc9f3f4da New translations messages.xlf (Thai)
[ci skip]
2021-03-07 10:13:32 +01:00
Jonas Winkler
c4b31f2f72 New translations messages.xlf (English, United Kingdom)
[ci skip]
2021-03-07 10:13:30 +01:00
Jonas Winkler
6c6b0511a6 New translations messages.xlf (Latin)
[ci skip]
2021-03-07 10:13:28 +01:00
jonaswinkler
6d20fc14ab updates messages 2021-03-07 10:08:45 +01:00
jonaswinkler
895c10600d add ru-ru locale 2021-03-07 10:07:42 +01:00
Jonas Winkler
c024efa578 Merge pull request #710 from jonaswinkler/l10n_dev
New Crowdin updates
2021-03-07 10:00:08 +01:00
Jonas Winkler
0022588b02 New translations messages.xlf (Russian)
[ci skip]
2021-03-07 02:58:09 +01:00
Jonas Winkler
47ec3d9149 New translations django.po (Russian)
[ci skip]
2021-03-07 02:58:08 +01:00
Jonas Winkler
811b82181d New translations messages.xlf (Russian)
[ci skip]
2021-03-07 01:59:13 +01:00
Jonas Winkler
32318ddbf6 Merge pull request #707 from jonaswinkler/l10n_dev
New Crowdin updates
2021-03-06 23:59:22 +01:00
Jonas Winkler
65a416fdae New translations django.po (French)
[ci skip]
2021-03-06 23:56:37 +01:00
Jonas Winkler
58d1f98368 New translations messages.xlf (French)
[ci skip]
2021-03-06 23:56:36 +01:00
Jonas Winkler
4cd7bf7123 New translations django.po (Spanish)
[ci skip]
2021-03-06 23:56:35 +01:00
Jonas Winkler
d4dea13eee New translations messages.xlf (Spanish)
[ci skip]
2021-03-06 23:56:34 +01:00
Jonas Winkler
507cdca757 New translations django.po (Czech)
[ci skip]
2021-03-06 23:56:33 +01:00
Jonas Winkler
27bfb2d3b0 New translations messages.xlf (Czech)
[ci skip]
2021-03-06 23:56:32 +01:00
Jonas Winkler
43118a1ffd New translations django.po (German)
[ci skip]
2021-03-06 23:56:30 +01:00
Jonas Winkler
729b305860 New translations messages.xlf (German)
[ci skip]
2021-03-06 23:56:29 +01:00
Jonas Winkler
72950d7872 New translations django.po (Hungarian)
[ci skip]
2021-03-06 23:56:28 +01:00
Jonas Winkler
364ffe8f38 New translations messages.xlf (Hungarian)
[ci skip]
2021-03-06 23:56:27 +01:00
Jonas Winkler
84d822b497 New translations django.po (Italian)
[ci skip]
2021-03-06 23:56:26 +01:00
Jonas Winkler
df99035c93 New translations messages.xlf (Italian)
[ci skip]
2021-03-06 23:56:25 +01:00
Jonas Winkler
63fc68cf83 New translations django.po (Dutch)
[ci skip]
2021-03-06 23:56:23 +01:00
Jonas Winkler
9c742fbf08 New translations messages.xlf (Romanian)
[ci skip]
2021-03-06 23:56:23 +01:00
Jonas Winkler
9965c35d2f New translations messages.xlf (Dutch)
[ci skip]
2021-03-06 23:56:21 +01:00
Jonas Winkler
0af567bc72 New translations messages.xlf (Portuguese)
[ci skip]
2021-03-06 23:56:20 +01:00
Jonas Winkler
0959a4e915 New translations django.po (Russian)
[ci skip]
2021-03-06 23:56:19 +01:00
Jonas Winkler
83be71f622 New translations messages.xlf (Russian)
[ci skip]
2021-03-06 23:56:18 +01:00
Jonas Winkler
9c985b36b3 New translations django.po (Chinese Simplified)
[ci skip]
2021-03-06 23:56:16 +01:00
Jonas Winkler
4ef0e20b5c New translations messages.xlf (Chinese Simplified)
[ci skip]
2021-03-06 23:56:15 +01:00
Jonas Winkler
d1cef044e5 New translations django.po (Portuguese, Brazilian)
[ci skip]
2021-03-06 23:56:14 +01:00
Jonas Winkler
f1d4860c79 New translations messages.xlf (Portuguese, Brazilian)
[ci skip]
2021-03-06 23:56:13 +01:00
Jonas Winkler
f1d6dd7d36 New translations django.po (English, United Kingdom)
[ci skip]
2021-03-06 23:56:08 +01:00
Jonas Winkler
46ae7f16dd New translations messages.xlf (English, United Kingdom)
[ci skip]
2021-03-06 23:56:07 +01:00
Jonas Winkler
5da441a001 New translations django.po (Portuguese)
[ci skip]
2021-03-06 23:56:05 +01:00
Jonas Winkler
d495bfab51 New translations django.po (Romanian)
[ci skip]
2021-03-06 23:56:03 +01:00
Michael Shamoon
4b4f3e76cc Re-merging this branch with Reverted dev 2021-03-06 14:50:38 -08:00
Michael Shamoon
ab0e97c1a0 Merge remote-tracking branch 'upstream/dev' into feature/popover-previews 2021-03-06 14:47:10 -08:00
jonaswinkler
381af329bd fix language codes 2021-03-06 23:40:29 +01:00
Michael Shamoon
4a0d17fc57 Merge branch 'feature/popover-previews' of https://github.com/shamoon/paperless-ng into feature/popover-previews 2021-03-06 14:39:53 -08:00
Michael Shamoon
e61f042547 Remove metadata API calls 2021-03-06 14:38:47 -08:00
Michael Shamoon
77815af5fb Update angular.json 2021-03-06 14:29:24 -08:00
jonaswinkler
e9cdb21164 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-06 23:25:07 +01:00
Jonas Winkler
59bc467c50 Update README.md 2021-03-06 22:52:52 +01:00
Jonas Winkler
254dc62ca6 Update README.md 2021-03-06 22:50:35 +01:00
Jonas Winkler
253bde4e25 Merge pull request #705 from jonaswinkler/l10n_dev
New Crowdin updates
2021-03-06 22:47:27 +01:00
jonaswinkler
ea9e852216 fix a failing test case 2021-03-06 22:41:35 +01:00
Jonas Winkler
89150e99bf New translations messages.xlf (Xhosa)
[ci skip]
2021-03-06 22:40:29 +01:00
Jonas Winkler
5fd05f3d20 New translations django.po (French)
[ci skip]
2021-03-06 22:40:27 +01:00
Jonas Winkler
6c1a5f1a98 New translations messages.xlf (French)
[ci skip]
2021-03-06 22:40:26 +01:00
Jonas Winkler
7815a011af New translations django.po (Spanish)
[ci skip]
2021-03-06 22:40:25 +01:00
Jonas Winkler
efca0083bf New translations messages.xlf (Spanish)
[ci skip]
2021-03-06 22:40:24 +01:00
Jonas Winkler
4c983b0e25 New translations django.po (Czech)
[ci skip]
2021-03-06 22:40:23 +01:00
Jonas Winkler
d81f64ec06 New translations messages.xlf (Czech)
[ci skip]
2021-03-06 22:40:22 +01:00
Jonas Winkler
5735313392 New translations django.po (German)
[ci skip]
2021-03-06 22:40:20 +01:00
Jonas Winkler
259b2fe429 New translations messages.xlf (German)
[ci skip]
2021-03-06 22:40:19 +01:00
Jonas Winkler
bb739a55a5 New translations django.po (Hungarian)
[ci skip]
2021-03-06 22:40:18 +01:00
Jonas Winkler
7436b75566 New translations messages.xlf (Hungarian)
[ci skip]
2021-03-06 22:40:17 +01:00
Jonas Winkler
573ac882f5 New translations django.po (Italian)
[ci skip]
2021-03-06 22:40:15 +01:00
Jonas Winkler
7f70a886d5 New translations messages.xlf (Italian)
[ci skip]
2021-03-06 22:40:14 +01:00
Jonas Winkler
4a1c0c2971 New translations django.po (Dutch)
[ci skip]
2021-03-06 22:40:13 +01:00
Jonas Winkler
ad37ccd76d New translations messages.xlf (Dutch)
[ci skip]
2021-03-06 22:40:12 +01:00
Jonas Winkler
d3a4b17d59 New translations messages.xlf (Romanian)
[ci skip]
2021-03-06 22:40:10 +01:00
Jonas Winkler
4233d73569 New translations django.po (Portuguese)
[ci skip]
2021-03-06 22:40:09 +01:00
Jonas Winkler
6d9a0162b2 New translations django.po (Russian)
[ci skip]
2021-03-06 22:40:08 +01:00
Jonas Winkler
72f89de994 New translations messages.xlf (Russian)
[ci skip]
2021-03-06 22:40:07 +01:00
Jonas Winkler
bf01799d57 New translations django.po (Chinese Simplified)
[ci skip]
2021-03-06 22:40:05 +01:00
Jonas Winkler
6b63d3855b New translations messages.xlf (Chinese Simplified)
[ci skip]
2021-03-06 22:40:04 +01:00
Jonas Winkler
f0e0e780c5 New translations django.po (Portuguese, Brazilian)
[ci skip]
2021-03-06 22:40:03 +01:00
Jonas Winkler
646803b93e New translations messages.xlf (Portuguese, Brazilian)
[ci skip]
2021-03-06 22:40:02 +01:00
Jonas Winkler
3026b59af4 New translations django.po (Thai)
[ci skip]
2021-03-06 22:40:00 +01:00
Jonas Winkler
5adee00ca1 New translations messages.xlf (Thai)
[ci skip]
2021-03-06 22:39:59 +01:00
Jonas Winkler
47e887dac5 New translations django.po (English, United Kingdom)
[ci skip]
2021-03-06 22:39:58 +01:00
Jonas Winkler
70395a035c New translations messages.xlf (English, United Kingdom)
[ci skip]
2021-03-06 22:39:57 +01:00
Jonas Winkler
2f63dcb3b5 New translations django.po (Latin)
[ci skip]
2021-03-06 22:39:55 +01:00
Jonas Winkler
468ea09965 New translations messages.xlf (Latin)
[ci skip]
2021-03-06 22:39:54 +01:00
Jonas Winkler
b6a94d33b8 New translations django.po (Xhosa)
[ci skip]
2021-03-06 22:39:53 +01:00
Jonas Winkler
40c89a8a38 New translations messages.xlf (Portuguese)
[ci skip]
2021-03-06 22:39:52 +01:00
Jonas Winkler
92a61902b5 New translations django.po (Romanian)
[ci skip]
2021-03-06 22:39:51 +01:00
jonaswinkler
23ee01b1ce Merge branch 'crowdin-test' of github.com:jonaswinkler/paperless-ng into crowdin-test 2021-03-06 22:20:30 +01:00
jonaswinkler
fc727e0472 fix up language file locations 2021-03-06 22:20:06 +01:00
Jonas Winkler
9427c70b10 Update Crowdin configuration file 2021-03-06 22:13:12 +01:00
jonaswinkler
cba71ed8e0 fix config 2021-03-06 22:12:44 +01:00
jonaswinkler
728778bdf4 update crowdin.yml 2021-03-06 22:09:03 +01:00
Jonas Winkler
30a92e25c1 Merge pull request #703 from darmiel/patch-1
Installation Script: Check for Docker permissions on startup
2021-03-06 16:44:44 +01:00
Daniel
4862eb5e84 Use -e switch after docker permission check 2021-03-06 15:40:19 +01:00
Daniel
42fd62ecfd Changed spaces to tabs 2021-03-06 15:30:16 +01:00
Daniel
67437f7b9c Check if the user has permissions for Docker 2021-03-06 15:27:55 +01:00
jonaswinkler
6c961dfad9 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-06 12:59:29 +01:00
Jonas Winkler
b0b5f9e0a1 Merge pull request #702 from auanasgheps/patch-1
Update Italian Translation
2021-03-06 12:58:49 +01:00
Oliver Cervera
69df7f8ef6 Update Italian Translation
Minor change to Italian translation.
Manual PR since Transifex doesn't do it.
2021-03-06 12:55:24 +01:00
jonaswinkler
0bb0fc5a19 changelog 2021-03-06 12:42:07 +01:00
jonaswinkler
be15e86458 added a test case for title_content filter 2021-03-06 12:41:59 +01:00
jonaswinkler
24eff6b02b documentation 2021-03-06 12:28:41 +01:00
jonaswinkler
ddb96829ca fix typo 2021-03-06 12:08:52 +01:00
jonaswinkler
4290f38e81 fix closing all documents from sidebar 2021-03-06 12:00:58 +01:00
jonaswinkler
ee4a5d53ad instructions on building the docker image 2021-03-06 11:47:11 +01:00
jonaswinkler
1f4d2e76d7 dark mode for color picker 2021-03-05 23:36:09 +01:00
jonaswinkler
9816efb816 Revert "Merge pull request #628 from shamoon/feature/popover-previews"
This reverts commit 8da1521508, reversing
changes made to a7846925b1.
2021-03-05 23:19:44 +01:00
Jonas Winkler
8da1521508 Merge pull request #628 from shamoon/feature/popover-previews
Feature: popover document previews
2021-03-05 21:34:19 +01:00
Jonas Winkler
a7846925b1 Merge pull request #699 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_en_GB
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'en_GB'
2021-03-05 09:36:52 +01:00
Jonas Winkler
d678bd71e8 Merge pull request #700 from jonaswinkler/translations_src-ui-messages-xlf--dev_en_GB
Translate '/src-ui/messages.xlf' in 'en_GB'
2021-03-05 09:36:39 +01:00
transifex-integration[bot]
67754efd36 Translate /src-ui/messages.xlf in en_GB
translation completed for the source file '/src-ui/messages.xlf'
on the 'en_GB' language.
2021-03-05 08:36:32 +00:00
transifex-integration[bot]
80e0fc21cc Apply translations in en_GB
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'en_GB' language.
2021-03-05 08:36:30 +00:00
Jonas Winkler
1025f6ccb5 Merge pull request #698 from siancu/dev
Postgres won't run without a password.
2021-03-04 16:44:49 +01:00
Stelian Iancu
ede06f4fd4 Postgres won't run without a password. 2021-03-04 16:26:03 +01:00
Jonas Winkler
6dd0de0201 Merge pull request #694 from jonaswinkler/translations_src-ui-messages-xlf--dev_nl_NL
Translate '/src-ui/messages.xlf' in 'nl_NL'
2021-03-04 11:32:56 +01:00
transifex-integration[bot]
7f01ead374 Translate /src-ui/messages.xlf in nl_NL
translation completed for the source file '/src-ui/messages.xlf'
on the 'nl_NL' language.
2021-03-04 10:25:24 +00:00
Michael Shamoon
cba507258d Merge remote-tracking branch 'upstream/dev' into feature/popover-previews 2021-03-03 21:19:51 -08:00
Michael Shamoon
ae93999f6a Simplify preview tooltip since its delayed 2021-03-03 21:14:24 -08:00
Michael Shamoon
1ff7a89a35 Larger popover preview size 2021-03-03 20:49:14 -08:00
Michael Shamoon
31cd32406c Refactor popover css to single file 2021-03-03 20:49:02 -08:00
Michael Shamoon
e5f750dcbe Fix double scrollbar, I think 2021-03-03 20:42:50 -08:00
jonaswinkler
59ecff8701 test case for the migration 2021-03-04 00:16:24 +01:00
jonaswinkler
80e91cde70 changelog 2021-03-04 00:10:15 +01:00
jonaswinkler
b1c2c79d80 update docs 2021-03-03 23:58:53 +01:00
jonaswinkler
5b2576ace4 remove test cases #677 2021-03-03 23:55:25 +01:00
Jonas Winkler
da9f370924 Merge pull request #677 from skuzzle/dev
Add the possibility to customize the remote user header name
2021-03-03 23:54:33 +01:00
jonaswinkler
e56daf07d4 Merge branch 'master' into dev 2021-03-03 23:46:37 +01:00
jonaswinkler
9e3caa57ec fix quoting 2021-03-03 23:44:43 +01:00
jonaswinkler
4fd7b9ef1f Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-03-03 23:44:15 +01:00
jonaswinkler
12235cc853 fixes #689 2021-03-03 23:35:26 +01:00
Jonas Winkler
56e4264394 Merge pull request #680 from jonaswinkler/translations_src-ui-messages-xlf--dev_it
Translate '/src-ui/messages.xlf' in 'it'
2021-03-03 09:39:50 +01:00
Jonas Winkler
baf8203b80 Merge pull request #681 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_it
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'it'
2021-03-03 09:39:39 +01:00
transifex-integration[bot]
41d966895f Apply translations in it
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'it' language.
2021-03-03 07:30:59 +00:00
transifex-integration[bot]
551792274f Translate /src-ui/messages.xlf in it
translation completed for the source file '/src-ui/messages.xlf'
on the 'it' language.
2021-03-03 07:30:31 +00:00
jonaswinkler
c7abdb61e8 added remote user auth test 2021-03-02 23:19:06 +01:00
Simon Taddiken
72ebe7df58 Improve documentation 2021-03-02 10:21:50 +01:00
Simon Taddiken
97f3c214e8 Add the possibility to customize the remote user header name
Inspired by the discussion here https://github.com/jonaswinkler/paperless-ng/discussions/639#discussion-3242017 it is worthwhile to be able to customize the header name that is used for authentication as its name is not really standardized.
2021-03-02 09:07:42 +01:00
Jonas Winkler
47842bac1c Merge pull request #665 from holzhannes/patch-1
Update to status of affiliateded projects
2021-03-02 00:39:13 +01:00
Jonas Winkler
ede498fa52 Merge pull request #671 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_nl_NL
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'nl_NL'
2021-03-01 16:37:53 +01:00
transifex-integration[bot]
c8a06416e7 Apply translations in nl_NL
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'nl_NL' language.
2021-03-01 15:30:27 +00:00
Jonas Winkler
5fea7f2bc4 Merge pull request #666 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_de
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'de'
2021-02-28 21:48:20 +01:00
Jonas Winkler
15420137fa Merge pull request #667 from jonaswinkler/translations_src-ui-messages-xlf--dev_de
Translate '/src-ui/messages.xlf' in 'de'
2021-02-28 21:48:10 +01:00
transifex-integration[bot]
73791e0e50 Translate /src-ui/messages.xlf in de
translation completed for the source file '/src-ui/messages.xlf'
on the 'de' language.
2021-02-28 20:47:42 +00:00
transifex-integration[bot]
7af0f12e6b Apply translations in de
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-28 20:47:14 +00:00
HolzHannes
895f9c911b Update to status of affiliateded projects 2021-02-28 20:23:38 +01:00
Jonas Winkler
204f47265b Merge pull request #663 from jonaswinkler/translations_src-ui-messages-xlf--dev_ro
Translate '/src-ui/messages.xlf' in 'ro'
2021-02-28 19:54:45 +01:00
transifex-integration[bot]
b240b4821a Translate /src-ui/messages.xlf in ro
translation completed for the source file '/src-ui/messages.xlf'
on the 'ro' language.
2021-02-28 18:04:39 +00:00
Jonas Winkler
249f6e4f27 Merge pull request #661 from jonaswinkler/translations_src-ui-messages-xlf--dev_pt_BR
Translate '/src-ui/messages.xlf' in 'pt_BR'
2021-02-28 18:02:10 +01:00
Jonas Winkler
32facd2877 Merge pull request #662 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_pt_BR
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'pt_BR'
2021-02-28 18:01:23 +01:00
transifex-integration[bot]
fd3cb6e2ca Apply translations in pt_BR
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'pt_BR' language.
2021-02-28 16:59:47 +00:00
transifex-integration[bot]
1df6d857e5 Translate /src-ui/messages.xlf in pt_BR
translation completed for the source file '/src-ui/messages.xlf'
on the 'pt_BR' language.
2021-02-28 16:59:35 +00:00
Jonas Winkler
3b9c2fd4d2 Merge pull request #660 from jonaswinkler/translations_src-ui-messages-xlf--dev_fr
Translate '/src-ui/messages.xlf' in 'fr'
2021-02-28 17:38:14 +01:00
Jonas Winkler
baa130e646 Merge pull request #659 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_fr
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'fr'
2021-02-28 17:38:04 +01:00
transifex-integration[bot]
c3ec40189f Translate /src-ui/messages.xlf in fr
translation completed for the source file '/src-ui/messages.xlf'
on the 'fr' language.
2021-02-28 16:36:24 +00:00
transifex-integration[bot]
686ae5b213 Apply translations in fr
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'fr' language.
2021-02-28 16:35:20 +00:00
jonaswinkler
5c19a8bb70 improved color generation logic 2021-02-28 16:35:02 +01:00
jonaswinkler
efa7b7b0b5 filter by title or title+content fixes #636 2021-02-28 16:19:41 +01:00
jonaswinkler
e466c31bb3 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-02-28 13:39:17 +01:00
Jonas Winkler
942acf5940 Merge pull request #658 from jonaswinkler/translations_src-ui-messages-xlf--dev_ro
Translate '/src-ui/messages.xlf' in 'ro'
2021-02-28 13:39:01 +01:00
Jonas Winkler
b38d8d8640 Merge pull request #657 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_ro
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'ro'
2021-02-28 13:38:35 +01:00
transifex-integration[bot]
c4c8a96f25 Translate /src-ui/messages.xlf in ro
translation completed for the source file '/src-ui/messages.xlf'
on the 'ro' language.
2021-02-28 12:36:01 +00:00
transifex-integration[bot]
65377d881c Apply translations in ro
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'ro' language.
2021-02-28 12:35:21 +00:00
jonaswinkler
b847e0c65e changelog, documentation, version bump 2021-02-28 13:20:31 +01:00
jonaswinkler
6ab884a95c update dependencies 2021-02-28 13:01:26 +01:00
jonaswinkler
4b17a485ae update messages and typo fix 2021-02-28 12:42:46 +01:00
jonaswinkler
e5a4715161 add Romanian locale configuration 2021-02-28 12:40:25 +01:00
Jonas Winkler
53b7b14467 Merge pull request #656 from jonaswinkler/translations_src-ui-messages-xlf--dev_ro
Translate '/src-ui/messages.xlf' in 'ro'
2021-02-28 12:02:42 +01:00
transifex-integration[bot]
5eeb012557 Translate /src-ui/messages.xlf in ro
translation completed for the source file '/src-ui/messages.xlf'
on the 'ro' language.
2021-02-28 11:02:26 +00:00
Jonas Winkler
cf9884aaba Merge pull request #655 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_ro
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'ro'
2021-02-28 11:47:35 +01:00
transifex-integration[bot]
d811cab57e Apply translations in ro
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'ro' language.
2021-02-28 10:33:26 +00:00
jonaswinkler
d5a3443076 Merge branch 'fix/issue-603' into dev 2021-02-27 11:25:21 +01:00
jonaswinkler
bd11d3a34f small changes 2021-02-27 11:24:58 +01:00
jonaswinkler
5f6a202d65 Merge remote-tracking branch 'shamoon/fix/issue-603' into fix/issue-603 2021-02-27 11:13:21 +01:00
jonaswinkler
6db3f7023f remove unused files 2021-02-26 23:24:13 +01:00
Michael Shamoon
8a64898798 TypeScript fixes 2021-02-26 14:16:31 -08:00
Michael Shamoon
6ad14d63d6 Consistency please 2021-02-26 14:11:25 -08:00
Michael Shamoon
a26150ca40 Hover state for close x button 2021-02-26 14:08:13 -08:00
Michael Shamoon
57c9066f81 Closing should only navigate if closing current document 2021-02-26 14:08:04 -08:00
jonaswinkler
fe3c3b8946 Merge branch 'dev' into fix/issue-603 2021-02-26 22:25:33 +01:00
Jonas Winkler
76686c22fc Merge pull request #648 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_nl_NL
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'nl_NL'
2021-02-26 22:24:23 +01:00
Jonas Winkler
bba4057616 Merge pull request #647 from jonaswinkler/translations_src-ui-messages-xlf--dev_nl_NL
Translate '/src-ui/messages.xlf' in 'nl_NL'
2021-02-26 22:24:11 +01:00
transifex-integration[bot]
10a1f34164 Apply translations in nl_NL
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'nl_NL' language.
2021-02-26 21:07:13 +00:00
transifex-integration[bot]
4516a5def5 Translate /src-ui/messages.xlf in nl_NL
translation completed for the source file '/src-ui/messages.xlf'
on the 'nl_NL' language.
2021-02-26 21:06:40 +00:00
Jonas Winkler
4ef4af452a Merge pull request #646 from C0nsultant/ansible-CI-stability
Ansible - Fix GitHub actions
2021-02-26 21:28:43 +01:00
Fabian Koller
8f1e95a0ce Fix GitHub actions
Follow-up to #630.
The check for the target git sha can does not work in the molecule step.
Instead expose the sha through an env variable in GitHub actions.
2021-02-26 19:39:44 +01:00
Jonas Winkler
824aea0c7b Merge pull request #645 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_en_GB
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'en_GB'
2021-02-26 15:42:54 +01:00
Jonas Winkler
7d54f0e336 Merge pull request #644 from jonaswinkler/translations_src-ui-messages-xlf--dev_en_GB
Translate '/src-ui/messages.xlf' in 'en_GB'
2021-02-26 15:42:42 +01:00
transifex-integration[bot]
961b47239b Apply translations in en_GB
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'en_GB' language.
2021-02-26 14:36:09 +00:00
transifex-integration[bot]
248f19a550 Translate /src-ui/messages.xlf in en_GB
translation completed for the source file '/src-ui/messages.xlf'
on the 'en_GB' language.
2021-02-26 14:36:04 +00:00
jonaswinkler
29e6529884 Merge branch 'master' into dev 2021-02-26 15:29:58 +01:00
jonaswinkler
cb4f822a19 documentation changes 2021-02-26 15:29:33 +01:00
jonaswinkler
2159fefc87 Merge branch 'master' into dev 2021-02-26 14:54:47 +01:00
jonaswinkler
d341bf14ff Merge branch 'feature-invert-thumb-option' into dev 2021-02-26 14:31:18 +01:00
Jonas Winkler
c793ac9c6c Merge pull request #638 from smaktacular/fix-dev-setup-docs
Added First-Time Step-by-Step procedure for Dev Env and CopyPaste lists of depen…
2021-02-26 14:08:43 +01:00
jonaswinkler
289589654b tests for API versioning 2021-02-26 13:21:33 +01:00
jonaswinkler
8cb9c9faf9 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-02-26 13:09:14 +01:00
jonaswinkler
1c0069e95e fix pycodestyle 2021-02-26 13:09:02 +01:00
Jonas Winkler
8f23642e7d Merge pull request #643 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_de
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'de'
2021-02-26 12:59:56 +01:00
Jonas Winkler
b59758379e Merge pull request #642 from jonaswinkler/translations_src-ui-messages-xlf--dev_de
Translate '/src-ui/messages.xlf' in 'de'
2021-02-26 12:59:47 +01:00
transifex-integration[bot]
d91267ee91 Apply translations in de
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-26 11:59:26 +00:00
transifex-integration[bot]
6196bc7b2e Translate /src-ui/messages.xlf in de
translation completed for the source file '/src-ui/messages.xlf'
on the 'de' language.
2021-02-26 11:59:16 +00:00
jonaswinkler
adfe5c123c update messages 2021-02-26 12:57:36 +01:00
jonaswinkler
573726d0b2 add Italian locale 2021-02-26 12:55:39 +01:00
Sebastian König
5cb95b6e55 Minor changes 2021-02-26 12:50:12 +01:00
Jonas Winkler
4ce79955f8 Merge pull request #640 from jonaswinkler/translations_src-ui-messages-xlf--dev_it
Translate '/src-ui/messages.xlf' in 'it'
2021-02-26 12:35:17 +01:00
Jonas Winkler
3d320b4e61 Merge pull request #641 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_it
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'it'
2021-02-26 12:34:58 +01:00
Bolko Schreiber
420feb3a52 lowered thumb brightness in non-inverted mode 2021-02-26 12:26:23 +01:00
transifex-integration[bot]
4d425cf7b8 Apply translations in it
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'it' language.
2021-02-26 11:24:55 +00:00
transifex-integration[bot]
f716300152 Translate /src-ui/messages.xlf in it
translation completed for the source file '/src-ui/messages.xlf'
on the 'it' language.
2021-02-26 11:02:24 +00:00
jonaswinkler
fea625e443 Merge branch 'dev' into fix/issue-603 2021-02-26 11:27:53 +01:00
jonaswinkler
068fdbd3f9 added versioning headers to the API 2021-02-26 11:13:28 +01:00
Sebastian König
fd2cbc13ff Added First-Time Step-by-Step procedure and CopyPaste lists of depeendencies to the docs 2021-02-26 09:49:36 +01:00
jonaswinkler
6a70b9c4b4 Merge branch 'feature-autocolor' into dev 2021-02-25 23:46:27 +01:00
jonaswinkler
4ee7d16d3e fix #600 2021-02-25 23:23:26 +01:00
jonaswinkler
6fd25da80d API documentation 2021-02-25 23:04:37 +01:00
jonaswinkler
2fb286cd43 shadows 2021-02-25 22:49:47 +01:00
jonaswinkler
3a75b2571a increase luminance threshold for better readability 2021-02-25 22:49:38 +01:00
jonaswinkler
66fe821b04 color picker fixes, random default color 2021-02-25 22:43:45 +01:00
jonaswinkler
ba478c6cbb tests 2021-02-25 22:17:18 +01:00
jonaswinkler
7b1145c75e bugfixes 2021-02-25 22:16:31 +01:00
jonaswinkler
b45630080b Merge branch 'dev' into feature-autocolor 2021-02-25 18:54:40 +01:00
jonaswinkler
170ddbc76f Merge branch 'dev' 2021-02-25 17:53:17 +01:00
Jonas Winkler
91b364f531 Merge pull request #630 from C0nsultant/ansible-CI-stability
Ansible - Improve CI stability
2021-02-25 17:50:40 +01:00
Michael Shamoon
aadcc85e73 fix forgot to remove class when nested nav-link-additional scss block 2021-02-25 07:48:48 -08:00
Michael Shamoon
548b1ea7ec Allow closing individual documents from sidebar 2021-02-25 07:43:02 -08:00
jonaswinkler
463696e6a8 changelog 2021-02-25 16:42:06 +01:00
Michael Shamoon
66f7ae3773 Remove redundant openDocuments and unused subscription 2021-02-25 07:29:51 -08:00
jonaswinkler
cc2b836646 fix date input error display 2021-02-25 16:29:49 +01:00
jonaswinkler
816a4c1887 fix an issue with the pt-br translation 2021-02-25 16:24:24 +01:00
jonaswinkler
604da64f52 fixed a regression in the date pipe 2021-02-25 16:22:50 +01:00
jonaswinkler
247edc89f0 fix default api version 2021-02-25 16:04:45 +01:00
Bolko Schreiber
6a1f5fb4cd cleanup css 2021-02-25 15:35:15 +01:00
Jonas Winkler
01c11f9fa0 Merge pull request #634 from chriscn/patch-1
Fixed typo of scripts
2021-02-25 15:32:59 +01:00
Christopher Nethercott
9434851c54 Fixed typo of scripts 2021-02-25 14:16:22 +00:00
Bolko Schreiber
a331e4b49c Set default value of DARK_MODE_THUMB_INVERTED to true 2021-02-25 14:52:55 +01:00
Bolko Schreiber
772f5cb1a8 Added option to invert thumbnails in dark mode 2021-02-25 13:57:45 +01:00
jonaswinkler
0fa9d71da8 fix duplicate IDs 2021-02-25 11:42:38 +01:00
jonaswinkler
3872c3c49b fixes #603 2021-02-25 11:42:21 +01:00
jonaswinkler
018b53db1b fix pt-br calendar 2021-02-25 11:37:39 +01:00
jonaswinkler
33ed2c8851 adjust frontend to use the new tag color api 2021-02-25 11:32:22 +01:00
jonaswinkler
c75e30fd28 validate hex color strings 2021-02-25 11:30:49 +01:00
jonaswinkler
7cc940a16c add text color generation based on luminance 2021-02-25 11:30:36 +01:00
Fabian Koller
ba853be00d Use local HEAD hash as installation target
GitHub actions' GITHUB_SHA provides unexpected hashes for manually
re-run pipelines. Instead rely on the commit as seen from the current
git history.
2021-02-25 06:59:27 +01:00
Fabian Koller
a720ed6a77 Do not require molecule teardown for success
CI failures should not be caused by intermittently failing docker
communication furing test teardown.
2021-02-25 06:22:59 +01:00
jonaswinkler
d84ca511d4 versioning for the tags API 2021-02-24 23:54:19 +01:00
jonaswinkler
95fe803bf6 remove colorhash (this will be done client side) 2021-02-24 23:52:52 +01:00
jonaswinkler
87c364a104 adjust migration 2021-02-24 23:52:25 +01:00
jonaswinkler
7233695824 Merge branch 'dev' into feature-autocolor 2021-02-24 23:10:59 +01:00
jonaswinkler
3d3300ac32 add versioning support to the API 2021-02-24 22:27:43 +01:00
Michael Shamoon
ef7775b658 Remove object tag contents 2021-02-24 13:08:39 -08:00
Michael Shamoon
a8776603c3 Fix removed setting 2021-02-24 13:08:31 -08:00
Michael Shamoon
ee92ab1136 Support large cards 2021-02-24 13:02:26 -08:00
Michael Shamoon
83c88ca472 Unused settings 2021-02-24 12:48:58 -08:00
Michael Shamoon
6f22297158 Merge remote-tracking branch 'upstream/dev' into feature/popover-previews 2021-02-24 12:41:44 -08:00
Jonas Winkler
df87599f1b Merge pull request #625 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_fr
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'fr'
2021-02-24 20:53:25 +01:00
Jonas Winkler
367b5e73cb Merge pull request #626 from jonaswinkler/translations_src-ui-messages-xlf--dev_fr
Translate '/src-ui/messages.xlf' in 'fr'
2021-02-24 20:53:13 +01:00
transifex-integration[bot]
d6cbea97f9 Translate /src-ui/messages.xlf in fr
translation completed for the source file '/src-ui/messages.xlf'
on the 'fr' language.
2021-02-24 19:30:01 +00:00
transifex-integration[bot]
e43ab23a45 Apply translations in fr
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'fr' language.
2021-02-24 19:29:33 +00:00
Michael Shamoon
706b1f4622 Remove ng2-pdf-viewer and use only native viewer 2021-02-24 11:26:39 -08:00
jonaswinkler
5b75321a31 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-02-24 20:02:10 +01:00
Michael Shamoon
2f2b5b90ea Dark mode compatibility 2021-02-24 19:58:51 +01:00
Jonas Winkler
cf3dedf4bb Merge pull request #622 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_en_GB
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'en_GB'
2021-02-24 19:43:59 +01:00
Jonas Winkler
d874145f52 Merge pull request #623 from jonaswinkler/translations_src-ui-messages-xlf--dev_en_GB
Translate '/src-ui/messages.xlf' in 'en_GB'
2021-02-24 19:43:51 +01:00
transifex-integration[bot]
800f4deb90 Translate /src-ui/messages.xlf in en_GB
translation completed for the source file '/src-ui/messages.xlf'
on the 'en_GB' language.
2021-02-24 18:43:40 +00:00
transifex-integration[bot]
c22af0a782 Apply translations in en_GB
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'en_GB' language.
2021-02-24 18:43:39 +00:00
Jonas Winkler
8465b8cfca Update README.md 2021-02-24 19:27:16 +01:00
jonaswinkler
6ae65e0ab4 changelog 2021-02-24 19:21:15 +01:00
jonaswinkler
6a64bf6fc1 changelog 2021-02-24 19:05:06 +01:00
jonaswinkler
f038bb90bb fixes for #600 2021-02-24 19:03:21 +01:00
jonaswinkler
a5ba14e446 version bump 2021-02-24 19:03:03 +01:00
Jonas Winkler
70f657beff Merge pull request #602 from C0nsultant/ansible-newocrvars
Ansible - Update exposed configuration variables
2021-02-24 18:56:20 +01:00
jonaswinkler
b1085c36ff Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-02-24 18:45:16 +01:00
jonaswinkler
1896aa7da1 use new date inputs for the date filter as well 2021-02-24 18:45:05 +01:00
Jonas Winkler
087856c535 Merge pull request #621 from jonaswinkler/translations_src-ui-messages-xlf--dev_pt_BR
Translate '/src-ui/messages.xlf' in 'pt_BR'
2021-02-24 18:26:55 +01:00
Jonas Winkler
27f1364caa Merge pull request #620 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_pt_BR
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'pt_BR'
2021-02-24 18:26:33 +01:00
transifex-integration[bot]
bec5365ac9 Translate /src-ui/messages.xlf in pt_BR
translation completed for the source file '/src-ui/messages.xlf'
on the 'pt_BR' language.
2021-02-24 17:18:26 +00:00
transifex-integration[bot]
0db14d6f74 Apply translations in pt_BR
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'pt_BR' language.
2021-02-24 17:18:07 +00:00
Jonas Winkler
ccb44e1bd1 Merge pull request #618 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_de
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'de'
2021-02-24 18:11:48 +01:00
Jonas Winkler
8c3db627e4 Merge pull request #619 from jonaswinkler/translations_src-ui-messages-xlf--dev_de
Translate '/src-ui/messages.xlf' in 'de'
2021-02-24 18:11:36 +01:00
transifex-integration[bot]
56204f8497 Translate /src-ui/messages.xlf in de
translation completed for the source file '/src-ui/messages.xlf'
on the 'de' language.
2021-02-24 17:10:24 +00:00
transifex-integration[bot]
fc75e88cb7 Apply translations in de
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-24 17:10:20 +00:00
jonaswinkler
d2719b5309 added a missing string 2021-02-24 18:08:55 +01:00
jonaswinkler
e5254abfcd Merge remote-tracking branch 'origin/master' into dev 2021-02-24 18:07:57 +01:00
jonaswinkler
96b8d35a00 update dependencies 2021-02-24 18:00:35 +01:00
jonaswinkler
035b0a449b use ng-bootstrap date selector, with proper formatting/parsing according to the current locale #177 2021-02-24 18:00:26 +01:00
jonaswinkler
6b20177b09 update README.md 2021-02-24 17:41:32 +01:00
jonaswinkler
15861ea41a add translation strings for portuguese 2021-02-24 16:50:05 +01:00
jonaswinkler
d836756e19 fix up quoting (hope this gets pushed into transifex 2021-02-24 16:43:55 +01:00
jonaswinkler
191d9a7b2b add configuration for pt-br 2021-02-24 16:39:41 +01:00
Jonas Winkler
0394cd7631 Merge pull request #617 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_pt_BR
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'pt_BR'
2021-02-24 16:19:00 +01:00
transifex-integration[bot]
c1ddff4ee5 Apply translations in pt_BR
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'pt_BR' language.
2021-02-24 15:00:14 +00:00
Jonas Winkler
55cc428cd3 Merge pull request #616 from jonaswinkler/translations_src-ui-messages-xlf--dev_pt_BR
Translate '/src-ui/messages.xlf' in 'pt_BR'
2021-02-24 15:03:51 +01:00
transifex-integration[bot]
39505c9764 Translate /src-ui/messages.xlf in pt_BR
translation completed for the source file '/src-ui/messages.xlf'
on the 'pt_BR' language.
2021-02-24 13:33:41 +00:00
Jonas Winkler
887a2f9be1 Merge pull request #615 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_de
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'de'
2021-02-24 12:53:22 +01:00
transifex-integration[bot]
1328fa9e5e Apply translations in de
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-24 11:52:24 +00:00
Jonas Winkler
513830f319 Merge pull request #612 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_fr
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'fr'
2021-02-24 11:13:36 +01:00
transifex-integration[bot]
440abcda7c Apply translations in fr
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'fr' language.
2021-02-24 09:09:47 +00:00
Jonas Winkler
6f3fdbecea Merge pull request #611 from bish0polis/patch-1
Update README.md
2021-02-24 09:40:50 +01:00
Bishop Clark
3a46cc33ee Update README.md
Like 'stuff' and 'traffic', 'mail' gets no 's'.
2021-02-23 22:09:12 -08:00
Jonas Winkler
ae3eb84e01 Merge pull request #600 from joelnordell/apple-touch-icon
Add apple-touch-icon for iOS devices "Add to Home Screen"
2021-02-23 16:18:56 +01:00
jonaswinkler
7a73e18596 fix pycodestyle and messages 2021-02-23 13:21:11 +01:00
jonaswinkler
c89779e8fc Merge branch 'master' into dev 2021-02-23 13:17:23 +01:00
jonaswinkler
5ca8d10d04 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-02-23 13:17:20 +01:00
jonaswinkler
1ce6aef57c Merge branch 'master' of github.com:jonaswinkler/paperless-ng 2021-02-23 13:17:08 +01:00
jonaswinkler
49b23aafa3 documentation typos 2021-02-23 13:16:56 +01:00
jonaswinkler
f888647b12 front end support for displaying error messages about regular expressions. 2021-02-23 13:13:38 +01:00
jonaswinkler
065ff6eaf5 rename some steps 2021-02-23 13:13:12 +01:00
Jonas Winkler
bf976fe188 Merge pull request #608 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_de
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'de'
2021-02-23 13:12:29 +01:00
transifex-integration[bot]
5ce73574c8 Apply translations in de
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-23 12:12:00 +00:00
jonaswinkler
5d94a983d2 move codestyle checks into separate job 2021-02-23 13:09:41 +01:00
Jonas Winkler
25366af4bb Update CONTRIBUTING.md 2021-02-23 13:03:20 +01:00
jonaswinkler
f397c5472c validation for regular expressions on matching models #605 2021-02-23 12:35:24 +01:00
Fabian Koller
6273eb0061 Handle OCR languages with hyphens
See https://github.com/jonaswinkler/paperless-ng/issues/584#issuecomment-782830878
2021-02-23 08:48:46 +01:00
Fabian Koller
734fd7c0fc Update exposed configuration variables
Include the newly added OCR clean and deskew parameters
2021-02-23 08:35:10 +01:00
Joel Nordell
8797a58efc Add apple-touch-icon for iOS devices "Add to Home Screen" 2021-02-22 22:25:10 -06:00
Michael Shamoon
5306d58991 Dark mode + sizing tweaks 2021-02-07 08:19:00 -08:00
Michael Shamoon
dcb17f2f21 hide on card mouseleave 2021-02-06 14:54:36 -08:00
Michael Shamoon
37b3a30619 Better minimum size for native viewer 2021-02-06 14:30:44 -08:00
Michael Shamoon
b0fed61a01 Pre-load pdf contents 2021-02-06 14:28:26 -08:00
Michael Shamoon
4a471138b2 Dark mode popover arrow fixes 2021-02-06 14:25:53 -08:00
Michael Shamoon
41540a3a5f Document popover previews 2021-02-06 00:40:18 -08:00
jayme-github
26784a5325 Add automatic coloring of tags
Please see this as proposal on how to implement automatic/random colors
for tags while keeping the current set of hard coded colors in place (at
least in the frontend).

Bear with me as I have even less Angular knowledge than Django and just
tried to get away with as few changes as possible. :-) AIUI you want to
change to a color picking system anyways in the future, which could also
play well with this.

fixes #51
2020-12-04 10:05:47 +01:00
176 changed files with 42756 additions and 6405 deletions

View File

@@ -47,8 +47,9 @@ jobs:
molecule converge
molecule idempotence
molecule verify
molecule destroy
working-directory: "${{ github.repository }}"
env:
TARGET_GITHUB_SHA: "${{ github.event.pull_request.head.sha }}"
# # https://galaxy.ansible.com/docs/contributing/importing.html
# release:
# runs-on: ubuntu-latest

View File

@@ -20,7 +20,7 @@ jobs:
name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: 3.7
-
name: Get pip cache dir
id: pip-cache
@@ -49,6 +49,39 @@ jobs:
name: documentation
path: docs/_build/html/
codestyle:
runs-on: ubuntu-20.04
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
-
name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
-
name: Persistent Github pip cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip${{ matrix.python-version }}
-
name: Install dependencies
run: |
pip install --upgrade pipenv
pipenv install --system --dev --ignore-pipfile
-
name: Codestyle
run: |
cd src/
pycodestyle
tests:
runs-on: ubuntu-20.04
strategy:
@@ -76,7 +109,7 @@ jobs:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip${{ matrix.python-version }}
-
name: Prepare tests
name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript optipng
@@ -87,11 +120,6 @@ jobs:
run: |
cd src/
pytest
-
name: Codestyle
run: |
cd src/
pycodestyle
-
name: Publish coverage results
if: matrix.python-version == '3.8'
@@ -130,7 +158,7 @@ jobs:
path: src/documents/static/frontend/
build-release:
needs: [frontend, documentation, tests]
needs: [frontend, documentation, tests, codestyle]
runs-on: ubuntu-20.04
steps:
-
@@ -240,7 +268,7 @@ jobs:
build-docker-image:
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/ng-'))
runs-on: ubuntu-latest
needs: [frontend, tests]
needs: [frontend, tests, codestyle]
steps:
-
name: Prepare

View File

@@ -9,11 +9,11 @@ If you want to implement something big: Please start a discussion about that in
## Python
Use python 3.6 for development. Paperless supports python 3.6, 3.7 and 3.8.
Paperless supports python 3.6, 3.7, 3.8 and 3.9.
## Branches
master always reflects the latest release.
master always reflects the latest release. Apart from changes to the documentation or readme, absolutely no functional changes on this branch in between releases.
dev contains all changes that will be part of the next release. Use this branch to start making your changes.

View File

@@ -11,25 +11,27 @@ RUN ./configure && make
FROM python:3.7-slim
# Binary dependencies
RUN apt-get update \
RUN echo "deb http://deb.debian.org/debian bullseye main" > /etc/apt/sources.list.d/bullseye.list \
&& apt-get update \
&& apt-get -y --no-install-recommends install \
# Basic dependencies
curl \
file \
# fonts for text file thumbnail generation
fonts-liberation \
# for making translations further down
gettext \
gnupg \
imagemagick \
gettext \
sudo \
tzdata \
# fonts for text file thumbnail generation
fonts-liberation \
# for Numpy
libatlas-base-dev \
libxslt1-dev \
mime-support \
# thumbnail size reduction
optipng \
sudo \
tzdata \
# Mime type detection
file \
libmagic-dev \
media-types \
# OCRmyPDF dependencies
ghostscript \
icc-profiles-free \
@@ -45,14 +47,7 @@ RUN apt-get update \
tesseract-ocr-spa \
unpaper \
zlib1g \
# This pulls in updated dependencies from bullseye to fix some issues with file type detection.
# TODO: Remove this once bullseye releases.
&& echo "deb http://deb.debian.org/debian bullseye main" > /etc/apt/sources.list.d/bullseye.list \
&& apt-get update \
&& apt-get install --no-install-recommends -y file libmagic-dev \
&& rm -rf /var/lib/apt/lists/* \
&& rm /etc/apt/sources.list.d/bullseye.list
&& rm -rf /var/lib/apt/lists/*
# copy jbig2enc
COPY --from=jbig2enc /usr/src/jbig2enc/src/.libs/libjbig2enc* /usr/local/lib/

12
Pipfile
View File

@@ -9,12 +9,12 @@ verify_ssl = true
name = "piwheels"
[packages]
dateparser = "~=0.7.6"
dateparser = "~=1.0.0"
django = "~=3.1.3"
django-cors-headers = "*"
django-extensions = "*"
django-filter = "~=2.4.0"
django-q = "~=1.3.4"
django-q = "==1.3.4"
djangorestframework = "~=3.12.2"
filelock = "*"
fuzzywuzzy = {extras = ["speedup"], version = "*"}
@@ -24,9 +24,8 @@ langdetect = "*"
# numpy 1.20.0 drops python 3.6 support
numpy = "~=1.19.5"
pathvalidate = "*"
# pinned to 8.1.0, since aarch64 wheels might not be available beyond that https://github.com/python-pillow/Pillow/issues/5202
pillow = "==8.1.0"
pikepdf = "~=2.5.0"
pillow = "~=8.1"
pikepdf = "~=2.5"
python-gnupg = "*"
python-dotenv = "*"
python-dateutil = "*"
@@ -52,8 +51,7 @@ uvicorn = {extras = ["standard"], version = "*"}
concurrent-log-handler = "*"
# uvloop 0.15+ incompatible with python 3.6
uvloop = "~=0.14.0"
# TODO: keep an eye on piwheel builds and update this once available (https://www.piwheels.org/project/cryptography/)
cryptography = "~=3.3.2"
cryptography = "~=3.4"
"pdfminer.six" = "*"
[dev-packages]

884
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
[![ci](https://github.com/jonaswinkler/paperless-ng/workflows/ci/badge.svg)](https://github.com/jonaswinkler/paperless-ng/actions)
![Ansible Role](https://github.com/jonaswinkler/paperless-ng/workflows/Ansible%20Role/badge.svg)
[![Crowdin](https://badges.crowdin.net/paperless-ng/localized.svg)](https://crowdin.com/project/paperless-ng)
[![Documentation Status](https://readthedocs.org/projects/paperless-ng/badge/?version=latest)](https://paperless-ng.readthedocs.io/en/latest/?badge=latest)
[![Gitter](https://badges.gitter.im/paperless-ng/community.svg)](https://gitter.im/paperless-ng/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Docker Hub Pulls](https://img.shields.io/docker/pulls/jonaswinkler/paperless-ng.svg)](https://hub.docker.com/r/jonaswinkler/paperless-ng)
@@ -11,12 +12,12 @@
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. These key points should help you decide whether Paperless-ng is something you would prefer over Paperless:
* Interface: The new front end is the main interface for paperless-ng, the old interface still exists but most customizations (such as thumbnails for the document list) have been removed.
* Interface: The new front end is the main interface for Paperless-ng, the old interface still exists but most customizations (such as thumbnails for the document list) have been removed.0
* Encryption: Paperless-ng does not support GnuPG anymore, since storing your data on encrypted file systems (that you optionally mount on demand) achieves about the same result.
* Resource usage: Paperless-ng does use a bit more resources than Paperless. Running the web server requires about 300MB of RAM or more, depending on the configuration. While adding documents, it requires about 300MB additional RAM, depending on the document. It still runs on Pi (many users do that), but it has been generally geared to better use the resources of more powerful systems.
* Resource usage: Paperless-ng does use a bit more resources than Paperless. Running the web server requires about 300MB of RAM or more, depending on the configuration. While adding documents, it requires about 300MB additional RAM, depending on the document. It still runs on Raspberry Pi (many users do that), but it has been generally geared to better use the resources of more powerful systems.
* API changes: If you rely on the REST API of paperless, some of its functionality has been changed.
For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation.
For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation, especially the section about the [0.9.0 release](https://paperless-ng.readthedocs.io/en/latest/changelog.html#paperless-ng-0-9-0).
# How it Works
@@ -24,7 +25,7 @@ Paperless does not control your scanner, it only helps you deal with what your s
1. Buy a document scanner that can write to a place on your network. If you need some inspiration, have a look at the [scanner recommendations](https://paperless-ng.readthedocs.io/en/latest/scanners.html) page. Set it up to "scan to FTP" or something similar. It should be able to push scanned images to a server without you having to do anything. Of course if your scanner doesn't know how to automatically upload the file somewhere, you can always do that manually. Paperless doesn't care how the documents get into its local consumption directory.
- Alternatively, you can use any of the mobile scanning apps out there. We have an app that allows you to share documents with paperless, if you're on Android. See the section on affiliated projects.
- Alternatively, you can use any of the mobile scanning apps out there. We have an app that allows you to share documents with paperless, if you're on Android. See the section on affiliated projects below.
2. Wait for paperless to process your files. OCR is expensive, and depending on the power of your machine, this might take a bit of time.
3. Use the web frontend to sift through the database and find what you want.
@@ -34,6 +35,8 @@ Here's what you get:
![Dashboard](https://github.com/jonaswinkler/paperless-ng/raw/master/docs/_static/screenshots/dashboard.png)
If you want to see paperless-ng in action, [more screenshots are available in the documentation](https://paperless-ng.readthedocs.io/en/latest/screenshots.html).
# Features
* Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents.
@@ -51,25 +54,23 @@ Here's what you get:
* Searching for similar documents ("More like this")
* Email processing: Paperless adds documents from your email accounts.
* Configure multiple accounts and filters for each account.
* When adding documents from mails, paperless can move these mails to a new folder, mark them as read, flag them as important or delete them.
* When adding documents from mail, paperless can move these mail to a new folder, mark them as read, flag them as important or delete them.
* Machine learning powered document matching.
* Paperless learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless.
* Optimized for multi core systems: Paperless-ng consumes multiple documents in parallel.
* The integrated sanity checker makes sure that your document archive is in good health.
If you want to see some screenshots of paperless-ng in action, [some are available in the documentation](https://paperless-ng.readthedocs.io/en/latest/screenshots.html).
# Getting started
The recommended way to deploy paperless is docker-compose. The files in the /docker/hub directory are configured to pull the image from Docker Hub.
Read the [documentation](https://paperless-ng.readthedocs.io/en/latest/setup.html#installation) on how to get started.
Alternatively, you can install the dependencies and setup apache and a database server yourself. The documenation has a step by step guide on how to do it.
Alternatively, you can install the dependencies and setup apache and a database server yourself. The documenation has a step by step guide on how to do it. Consider giving the Ansible role a shot, this essentially automates the entire bare metal installation process.
# Migrating to paperless-ng
# Migrating from Paperless to Paperless-ng
Read the section about [migration](https://paperless-ng.readthedocs.io/en/latest/setup.html#migration-to-paperless-ng) in the documentation. Its also entirely possible to go back to paperless by reverting the database migrations.
Read the section about [migration](https://paperless-ng.readthedocs.io/en/latest/setup.html#migration-to-paperless-ng) in the documentation. Its also entirely possible to go back to Paperless by reverting the database migrations.
# Documentation
@@ -77,9 +78,7 @@ The documentation for Paperless-ng is available on [ReadTheDocs](https://paperle
# Translation
Paperless is currently available in English, German, Dutch and French. Translation is coordinated at transifex: https://www.transifex.com/paperless/paperless-ng
If you want to see paperless in your own language, request that language at transifex and you can start translating after I approve the language.
Paperless is available in many different languages. Translation is coordinated at crowdin. If you want to help out by translating paperless into your language, please head over to https://github.com/jonaswinkler/paperless-ng/issues/212 for details!
# Feature Requests
@@ -93,7 +92,7 @@ For bugs please [open an issue](https://github.com/jonaswinkler/paperless-ng/iss
There's still lots of things to be done, just have a look at open issues & discussions. If you feel like contributing to the project, please do! Bug fixes and improvements to the front end (I just can't seem to get some of these CSS things right) are always welcome. The documentation has some basic information on how to get started.
If you want to implement something big: Please start a discussion about that in the issues! Maybe I've already had something similar in mind and we can make it happen together. However, keep in mind that the general roadmap is to make the existing features stable and get them tested. See the roadmap above.
If you want to implement something big: Please start a discussion about that! Maybe I've already had something similar in mind and we can make it happen together. However, keep in mind that the general roadmap is to make the existing features stable and get them tested.
# Affiliated Projects
@@ -104,9 +103,13 @@ Paperless has been around a while now, and people are starting to build stuff on
These projects also exist, but their status and compatibility with paperless-ng is unknown.
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows.
* [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance.
This project also exists, but needs updates to be compatile with paperless-ng.
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows.
Known issues on Mac: (Could not load reminders and documents)
# Important Note
Document scanners are typically used to scan sensitive documents. Things like your social insurance number, tax records, invoices, etc. Everything is stored in the clear without encryption. This means that Paperless should never be run on an untrusted host. Instead, I recommend that if you do want to use it, run it locally on a server in your own home.

View File

@@ -18,8 +18,9 @@ paperlessng_directory: /opt/paperless-ng
paperlessng_consumption_dir: "{{ paperlessng_directory }}/consumption"
paperlessng_data_dir: "{{ paperlessng_directory }}/data"
paperlessng_media_root: "{{ paperlessng_directory }}/media"
paperlessng_static_dir: "{{ paperlessng_directory }}/static"
paperlessng_staticdir: "{{ paperlessng_directory }}/static"
paperlessng_filename_format:
paperlessng_logging_dir: "{{ paperlessng_data_dir }}/log"
paperlessng_virtualenv: "{{ paperlessng_directory }}/.venv"
# Hosting & Security
@@ -36,12 +37,15 @@ paperlessng_enable_http_remote_user: False
paperlessng_ocr_languages:
- eng
paperlessng_ocr_mode: skip
paperlessng_ocr_clean: clean
paperlessng_ocr_deskew: True
paperlessng_ocr_rotate_pages: True
paperlessng_ocr_rotate_pages_threshold: 12
paperlessng_ocr_output_type: pdfa
paperlessng_ocr_pages: 0
paperlessng_ocr_image_dpi:
# see https://ocrmypdf.readthedocs.io/en/latest/api.html#ocrmypdf.ocr
paperlessng_ocr_user_args:
#- "deskew": True # https://github.com/jonaswinkler/paperless-ng/issues/231
- "optimize": 1
paperlessng_use_jbig2enc: True
paperlessng_big2enc_lossy: False
@@ -57,10 +61,11 @@ paperlessng_consumer_polling: 0
paperlessng_consumer_delete_duplicates: False
paperlessng_consumer_recursive: False
paperlessng_consumer_subdirs_as_tags: False
paperlessng_convert_memory_limit: 0
paperlessng_convert_tmpdir:
paperlessng_optimize_thumbnails: True
paperlessng_post_consume_script:
paperlessng_filename_date_order:
paperlessng_filename_parse_transforms:
paperlessng_thumbnail_font_name: /usr/share/fonts/liberation/LiberationSerif-Regular.ttf
paperlessng_ignore_dates: ""

View File

@@ -4,7 +4,7 @@
tasks:
- name: set current github commit as version when available
set_fact:
paperlessng_version: "{{ lookup('env', 'GITHUB_SHA') | default('master', True) }}"
paperlessng_version: "{{ lookup('env', 'TARGET_GITHUB_SHA') | default('master', True) }}"
- name: update to newest paperless-ng release
include_role:
name: ansible

View File

@@ -43,7 +43,7 @@
- name: install ocr languages
apt:
pkg: "{{ paperlessng_ocr_languages | map('regex_replace', '^(.*)$', 'tesseract-ocr-\\1') | list }}"
pkg: "{{ paperlessng_ocr_languages | map('regex_replace', '^(.*)$', 'tesseract-ocr-\\1') | map('replace', '_', '-') | list }}"
- name: set up notesalexp repository key (for jbig2enc)
apt_key:
@@ -256,7 +256,7 @@
- "{{ paperlessng_consumption_dir }}"
- "{{ paperlessng_data_dir }}"
- "{{ paperlessng_media_root }}"
- "{{ paperlessng_static_dir }}"
- "{{ paperlessng_staticdir }}"
- name: rename initial config
command:
@@ -280,9 +280,11 @@
- regexp: PAPERLESS_MEDIA_ROOT
line: "PAPERLESS_MEDIA_ROOT={{ paperlessng_media_root }}"
- regexp: PAPERLESS_STATICDIR
line: "PAPERLESS_STATICDIR={{ paperlessng_static_dir }}"
line: "PAPERLESS_STATICDIR={{ paperlessng_staticdir }}"
- regexp: PAPERLESS_FILENAME_FORMAT
line: "PAPERLESS_FILENAME_FORMAT={{ paperlessng_filename_format }}"
- regexp: PAPERLESS_LOGGING_DIR
line: "PAPERLESS_LOGGING_DIR={{ paperlessng_logging_dir }}"
# Hosting & Security
- regexp: PAPERLESS_SECRET_KEY
line: "PAPERLESS_SECRET_KEY={{ paperlessng_secret_key }}"
@@ -302,9 +304,17 @@
line: "PAPERLESS_ENABLE_HTTP_REMOTE_USER={{ paperlessng_enable_http_remote_user }}"
# OCR settings
- regexp: PAPERLESS_OCR_LANGUAGE
line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') }}"
line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') | replace('-','_') }}"
- regexp: PAPERLESS_OCR_MODE
line: "PAPERLESS_OCR_MODE={{ paperlessng_ocr_mode }}"
- regexp: PAPERLESS_OCR_CLEAN
line: "PAPERLESS_OCR_CLEAN={{ paperlessng_ocr_clean }}"
- regexp: PAPERLESS_OCR_DESKEW
line: "PAPERLESS_OCR_DESKEW={{ paperlessng_ocr_deskew }}"
- regexp: PAPERLESS_OCR_ROTATE_PAGES
line: "PAPERLESS_OCR_ROTATE_PAGES={{ paperlessng_ocr_rotate_pages }}"
- regexp: PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD
line: "PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD={{ paperlessng_ocr_rotate_pages_threshold }}"
- regexp: PAPERLESS_OCR_OUTPUT_TYPE
line: "PAPERLESS_OCR_OUTPUT_TYPE={{ paperlessng_ocr_output_type }}"
- regexp: PAPERLESS_OCR_PAGES
@@ -331,6 +341,10 @@
line: "PAPERLESS_CONSUMER_RECURSIVE={{ paperlessng_consumer_recursive }}"
- regexp: PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS
line: "PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS={{ paperlessng_consumer_subdirs_as_tags }}"
- regexp: PAPERLESS_CONVERT_MEMORY_LIMIT
line: "PAPERLESS_CONVERT_MEMORY_LIMIT={{ paperlessng_convert_memory_limit }}"
- regexp: PAPERLESS_CONVERT_TMPDIR
line: "PAPERLESS_CONVERT_TMPDIR={{ paperlessng_convert_tmpdir }}"
- regexp: PAPERLESS_OPTIMIZE_THUMBNAILS
line: "PAPERLESS_OPTIMIZE_THUMBNAILS={{ paperlessng_optimize_thumbnails }}"
- regexp: PAPERLESS_POST_CONSUME_SCRIPT

View File

@@ -1,3 +1,4 @@
commit_message: '[ci skip]'
files:
- source: /src/locale/en_US/LC_MESSAGES/django.po
translation: /src/locale/%locale_with_underscore%/LC_MESSAGES/django.po

View File

@@ -68,6 +68,17 @@ migrations() {
}
search_index() {
index_version=1
index_version_file=/usr/src/paperless/data/.index_version
if [[ (! -f "$index_version_file") || $(< $index_version_file) != "$index_version" ]]; then
echo "Search index out of date. Updating..."
sudo -HEu paperless python3 manage.py document_index reindex
echo $index_version | sudo -HEu paperless tee $index_version_file >/dev/null
fi
}
initialize() {
map_uidgid
@@ -87,6 +98,7 @@ initialize() {
migrations
search_index
}
install_languages() {

View File

@@ -184,17 +184,19 @@ Downgrades are possible. However, some updates also contain database migrations
In order to move back from a version that applied database migrations, you'll have to revert the database migration *before* downgrading,
and then downgrade paperless.
This table lists the most recent database migrations for each versions:
This table lists the compatible versions for each database migration number.
+---------+-------------------------+
| Version | Latest migration number |
+---------+-------------------------+
| 1.0.0 | 1011 |
+---------+-------------------------+
| 1.1.0 | 1011 |
+---------+-------------------------+
| 1.1.1 | 1012 |
+---------+-------------------------+
+------------------+-----------------+
| Migration number | Version range |
+------------------+-----------------+
| 1011 | 1.0.0 |
+------------------+-----------------+
| 1012 | 1.1.0 - 1.2.1 |
+------------------+-----------------+
| 1014 | 1.3.0 - 1.3.1 |
+------------------+-----------------+
| 1016 | 1.3.2 - current |
+------------------+-----------------+
Execute the following management command to migrate your database:

View File

@@ -10,14 +10,13 @@ easier.
Matching tags, correspondents and document types
################################################
After the consumer has tried to figure out what it could from the file name,
it starts looking at the content of the document itself. It will compare the
matching algorithms defined by every tag and correspondent already set in your
database to see if they apply to the text in that document. In other words,
if you defined a tag called ``Home Utility`` that had a ``match`` property of
``bc hydro`` and a ``matching_algorithm`` of ``literal``, Paperless will
automatically tag your newly-consumed document with your ``Home Utility`` tag
so long as the text ``bc hydro`` appears in the body of the document somewhere.
Paperless will compare the matching algorithms defined by every tag and
correspondent already set in your database to see if they apply to the text in
a document. In other words, if you defined a tag called ``Home Utility``
that had a ``match`` property of ``bc hydro`` and a ``matching_algorithm`` of
``literal``, Paperless will automatically tag your newly-consumed document with
your ``Home Utility`` tag so long as the text ``bc hydro`` appears in the body
of the document somewhere.
The matching logic is quite powerful, and supports searching the text of your
document with different algorithms, and as such, some experimentation may be

View File

@@ -147,93 +147,57 @@ The REST api provides three different forms of authentication.
Searching for documents
#######################
Paperless-ng offers API endpoints for full text search. These are as follows:
Full text searching is available on the ``/api/documents/`` endpoint. Two specific
query parameters cause the API to return full text search results:
``/api/search/``
================
* ``/api/documents/?query=your%20search%20query``: Search for a document using a full text query.
For details on the syntax, see :ref:`basic-usage_searching`.
Get search results based on a query.
* ``/api/documents/?more_like=1234``: Search for documents similar to the document with id 1234.
Query parameters:
Pagination works exactly the same as it does for normal requests on this endpoint.
* ``query``: The query string. See
`here <https://whoosh.readthedocs.io/en/latest/querylang.html>`_
for details on the syntax.
* ``page``: Specify the page you want to retrieve. Each page
contains 10 search results and the first page is ``page=1``, which
is the default if this is omitted.
Certain limitations apply to full text queries:
Result list object returned by the endpoint:
* Results are always sorted by search score. The results matching the query best will show up first.
.. code:: json
* Only a small subset of filtering parameters are supported.
Furthermore, each returned document has an additional ``__search_hit__`` attribute with various information
about the search results:
.. code::
{
"count": 1,
"page": 1,
"page_count": 1,
"corrected_query": "",
"count": 31,
"next": "http://localhost:8000/api/documents/?page=2&query=test",
"previous": null,
"results": [
...
{
"id": 123,
"title": "title",
"content": "content",
...
"__search_hit__": {
"score": 0.343,
"highlights": "text <span class=\"match\">Test</span> text",
"rank": 23
}
},
...
]
}
* ``count``: The approximate total number of results.
* ``page``: The page returned to you. This might be different from
the page you requested, if you requested a page that is behind
the last page. In that case, the last page is returned.
* ``page_count``: The total number of pages.
* ``corrected_query``: Corrected version of the query string. Can be null.
If not null, can be used verbatim to start a new query.
* ``results``: A list of result objects on the current page.
Result object:
.. code:: json
{
"id": 1,
"highlights": [
],
"score": 6.34234,
"rank": 23,
"document": {
}
}
* ``id``: the primary key of the found document
* ``highlights``: an object containing parsable highlights for the result.
See below.
* ``score``: The score assigned to the document. A higher score indicates a
better match with the query. Search results are sorted descending by score.
* ``rank``: the position of the document within the entire search results list.
* ``document``: The full json of the document, as returned by
``/api/documents/<id>/``.
Highlights object:
Highlights are provided as a list of fragments. A fragment is a longer section of
text from the original document.
Each fragment contains a list of strings, and some of them are marked as a highlight.
.. code:: json
[
[
{"text": "This is a sample text with a ", "highlight": false},
{"text": "highlighted", "highlight": true},
{"text": " word.", "highlight": false}
],
[
{"text": "Another", "highlight": true},
{"text": " fragment with a highlight.", "highlight": false}
]
]
A client may use this example to produce the following output:
... This is a sample text with a **highlighted** word. ... **Another** fragment with a highlight. ...
* ``score`` is an indication how well this document matches the query relative to the other search results.
* ``highlights`` is an excerpt from the document content and highlights the search terms with ``<span>`` tags as shown above.
* ``rank`` is the index of the search results. The first result will have rank 0.
``/api/search/autocomplete/``
=============================
@@ -284,3 +248,53 @@ The endpoint supports the following optional form fields:
The endpoint will immediately return "OK" if the document consumption process
was started successfully. No additional status information about the consumption
process itself is available, since that happens in a different process.
.. _api-versioning:
API Versioning
##############
The REST API is versioned since Paperless-ng 1.3.0.
* Versioning ensures that changes to the API don't break older clients.
* Clients specify the specific version of the API they wish to use with every request and Paperless will handle the request using the specified API version.
* Even if the underlying data model changes, older API versions will always serve compatible data.
* If no version is specified, Paperless will serve version 1 to ensure compatibility with older clients that do not request a specific API version.
API versions are specified by submitting an additional HTTP ``Accept`` header with every request:
.. code::
Accept: application/json; version=6
If an invalid version is specified, Paperless 1.3.0 will respond with "406 Not Acceptable" and an error message in the body.
Earlier versions of Paperless will serve API version 1 regardless of whether a version is specified via the ``Accept`` header.
If a client wishes to verify whether it is compatible with any given server, the following procedure should be performed:
1. Perform an *authenticated* request against any API endpoint. If the server is on version 1.3.0 or newer, the server will
add two custom headers to the response:
.. code::
X-Api-Version: 2
X-Version: 1.3.0
2. Determine whether the client is compatible with this server based on the presence/absence of these headers and their values if present.
API Changelog
=============
Version 1
---------
Initial API version.
Version 2
---------
* Added field ``Tag.color``. This read/write string field contains a hex color such as ``#a6cee3``.
* Added read-only field ``Tag.text_color``. This field contains the text color to use for a specific tag, which is either black or white depending on the brightness of ``Tag.color``.
* Removed field ``Tag.colour``.

View File

@@ -5,6 +5,129 @@
Changelog
*********
paperless-ng 1.4.0
##################
* Docker images now use tesseract 4.1.1, which should fix a series of issues with OCR.
* The full text search now displays results using the default document list. This enables
selection, filtering and bulk edit on search results.
* Changes
* Firefox only: Highlight search query in PDF previews.
* New URL pattern for accessing documents by ASN directly (http://<paperless>/asn/123)
* Added logging when executing pre- and post-consume scripts.
* Better error logging during document consumption.
* Updated python dependencies.
* Automatically inserts typed text when opening "Create new" dialogs on the document details page.
* Fixes
* Fixed an issue with null characters in the document content.
.. 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 :ref:`administration-index`).
.. note::
Some packages that paperless depends on are slowly dropping Python 3.6
support one after another, including the web server. Supporting Python
3.6 means that I cannot update these packages anymore.
At some point, paperless will drop Python 3.6 support. If using a bare
metal installation and you're still on Python 3.6, upgrade to 3.7 or newer.
If using docker, this does not affect you.
paperless-ng 1.3.2
##################
* Added translation into Portuguese.
* Changes
* The exporter now exports user accounts, mail accounts, mail rules and saved views as well.
* Fixes
* Minor layout issues with document cards and the log viewer.
* Fixed an issue with any/all/exact matching when characters used in regular expressions were used for the match.
paperless-ng 1.3.1
##################
* Added translation into Spanish and Russian.
* Other changes
* ISO-8601 date format will now always show years with 4 digits.
* Added the ability to search for a document with a specific ASN.
* The document cards now display ASN, types and dates in a more organized way.
* Added document previews when hovering over the preview button.
* Fixes
* The startup check for write permissions now works properly on NFS shares.
* Fixed an issue with the search results score indicator.
* Paperless was unable to generate thumbnails for encrypted PDF files and failed. Paperless will now generate a default thumbnail for these files.
* Fixed ``AUTO_LOGIN_USERNAME``: Unable to perform POST/PUT/DELETE requests and unable to receive WebSocket messages.
paperless-ng 1.3.0
##################
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 :ref:`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.
* Added translations into Italian and Romanian. Thank you!
* Close individual documents from the sidebar. Thanks to `Michael Shamoon`_.
* `BolkoSchreiber <https://github.com/BolkoSchreiber>`_ added an option to disable/enable thumbnail inversion in dark mode.
* `Simon Taddiken <https://github.com/skuzzle>`_ added the ability to customize the header used for remote user authentication with SSO applications.
* Bug fixes
* Fixed an issue with the auto matching algorithm when more than 256 tags were used.
paperless-ng 1.2.1
##################
* `Rodrigo Avelino <https://github.com/rodavelino>`_ translated Paperless into Portuguese (Brazil)!
* The date input fields now respect the currently selected date format.
* Added a fancy icon when adding paperless to the home screen on iOS devices. Thanks to `Joel Nordell <https://github.com/joelnordell>`_.
* When using regular expression matching, the regular expression is now validated before saving the tag/correspondent/type.
* Regression fix: Dates on the front end did not respect date locale settings in some cases.
paperless-ng 1.2.0
##################
@@ -29,17 +152,6 @@ paperless-ng 1.2.0
* Paperless no longer depends on ``libpoppler-cpp-dev``.
.. note::
Some packages that paperless depends on are slowly dropping Python 3.6
support one after another, including the web server. Supporting Python
3.6 means that I cannot update these packages anymore.
At some point, paperless will drop Python 3.6 support. If using a bare
metal installation and you're still on Python 3.6, upgrade to 3.7 or newer.
If using docker, this does not affect you.
paperless-ng 1.1.4
##################

View File

@@ -191,8 +191,28 @@ PAPERLESS_ENABLE_HTTP_REMOTE_USER=<bool>
Allows authentication via HTTP_REMOTE_USER which is used by some SSO
applications.
Defaults to `false` which disables this feature.
.. warning::
This will allow authentication by simply adding a ``Remote-User: <username>`` header
to a request. Use with care! You especially *must* ensure that any such header is not
passed from your proxy server to paperless.
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>`.
Defaults to `false` which disables this feature.
PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME=<str>
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_` followed by the
normalized actual header name.
Defaults to `HTTP_REMOTE_USER`.
.. _configuration-ocr:
OCR settings
@@ -278,7 +298,7 @@ PAPERLESS_OCR_DESKEW=<bool>
PAPERLESS_OCR_ROTATE_PAGES=<bool>
Tells paperless to correct page rotation (90°, 180° and 270° rotation).
If you notice that paperless is not rotating pages incorrectly rotated
If you notice that paperless is not rotating incorrectly rotated
pages (or vice versa), try adjusting the threshold up or down (see below).
Defaults to ``true``, which enables this feature.
@@ -287,7 +307,7 @@ PAPERLESS_OCR_ROTATE_PAGES=<bool>
PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD=<num>
Adjust the threshold for automatic page rotation by ``PAPERLESS_OCR_ROTATE_PAGES``.
This is an arbitrary value reported by tesseract. "15" is a very conservative value,
whereas "2" is a very aggressive option and will often result correctly rotated pages
whereas "2" is a very aggressive option and will often result in correctly rotated pages
being rotated as well.
Defaults to "12".
@@ -338,7 +358,7 @@ PAPERLESS_OCR_USER_ARGS=<json>
the API of OCRmyPDF, you have to specify these in a format that can be
passed to the API. See `the API reference of OCRmyPDF <https://ocrmypdf.readthedocs.io/en/latest/api.html#reference>`_
for valid parameters. All command line options are supported, but they
use underscores instead of dashed.
use underscores instead of dashes.
.. caution::

View File

@@ -5,29 +5,82 @@ Paperless development
This section describes the steps you need to take to start development on paperless-ng.
1. Check out the source from github. The repository is organized in the following way:
Check out the source from github. The repository is organized in the following way:
* ``master`` always represents the latest release and will only see changes
when a new release is made.
* ``dev`` contains the code that will be in the next release.
* ``feature-X`` contain bigger changes that will be in some release, but not
necessarily the next one.
Apart from that, the folder structure is as follows:
* ``master`` always represents the latest release and will only see changes
when a new release is made.
* ``dev`` contains the code that will be in the next release.
* ``feature-X`` contain bigger changes that will be in some release, but not
necessarily the next one.
* ``docs/`` - Documentation.
* ``src-ui/`` - Code of the front end.
* ``src/`` - Code of the back end.
* ``scripts/`` - Various scripts that help with different parts of development.
* ``docker/`` - Files required to build the docker image.
When making functional changes to paperless, *always* make your changes on the ``dev`` branch.
2. Install some dependencies.
Apart from that, the folder structure is as follows:
* Python 3.6.
* All dependencies listed in the :ref:`Bare metal route <setup-bare_metal>`
* redis. You can either install redis or use the included scritps/start-services.sh
to use docker to fire up a redis instance (and some other services such as tika,
gotenberg and a postgresql server).
* ``docs/`` - Documentation.
* ``src-ui/`` - Code of the front end.
* ``src/`` - Code of the back end.
* ``scripts/`` - Various scripts that help with different parts of development.
* ``docker/`` - Files required to build the docker image.
Initial setup and first start
=============================
After you forked and cloned the code from github you need to perform a 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 :ref:`Bare metal route <setup-bare_metal>`
2. Copy ``paperless.conf.example`` to ``paperless.conf`` and enable debug mode.
3. Install the Angular CLI interface:
.. code:: shell-session
$ npm install -g @angular/cli
4. Create ``consume`` and ``media`` folders in the cloned root folder.
.. code:: shell-session
mkdir -p consume media
5. You can now either ...
* install redis or
* use the included scripts/start-services.sh to use docker to fire up a redis instance (and some other services such as tika, gotenberg and a postgresql server) or
* spin up a bare redis container
.. code:: shell-session
docker run -d -p 6379:6379 -restart unless-stopped redis:latest
6. Install the python dependencies by performing in the src/ directory.
.. code:: shell-session
pipenv install --dev
7. 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 time only). From root folder:
.. code:: shell-session
compile-frontend.sh
8. Apply migrations and create a superuser for your dev instance:
.. code:: shell-session
python3 manage.py migrate
python3 manage.py createsuperuser
9. Now spin up the dev backend. Depending on which part of paperless you're developing for, you need to have some or all of them running.
.. code:: shell-session
python3 manage.py runserver & python3 manage.py document_consumer & python3 manage.py qcluster
10. Login with the superuser credentials provided in step 8 at ``http://localhost:8000`` to create a session that enables you to use the backend.
Backend development environment is now ready, to start Frontend development go to ``/src-ui`` and run ``ng serve``. From there you can use ``http://localhost:4200`` for a preview.
Back end development
====================
@@ -35,21 +88,18 @@ Back end development
The backend is a django application. I use PyCharm for development, but you can use whatever
you want.
Install the python dependencies by performing ``pipenv install --dev`` in the src/ directory.
This will also create a virtual environment, which you can enter with ``pipenv shell`` or
execute one-shot commands in with ``pipenv run``.
Copy ``paperless.conf.example`` to ``paperless.conf`` and enable debug mode.
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
* python3 manage.py qcluster
* python3 manage.py consumer
* python3 manage.py document_consumer
Depending on which part of paperless you're developing for, you need to have some or all of
them running.
To start them all:
.. code:: shell-session
python3 manage.py runserver & python3 manage.py document_consumer & python3 manage.py qcluster
Testing and code style:
@@ -62,7 +112,7 @@ Testing and code style:
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 make some lines fit, especially complicated IF cases. Append ``# NOQA: E501``
to disable this check for certain lines.
Front end development
@@ -109,6 +159,92 @@ This will build the front end and put it in a location from which the Django ser
it as static content. This way, you can verify that authentication is working.
Localization
============
Paperless is available in many different languages. Since paperless consists both of a django
application and an Angular front end, both these parts have to be translated separately.
Front end localization
----------------------
* The Angular front end does localization according to the `Angular documentation <https://angular.io/guide/i18n>`_.
* The source language of the project is "en_US".
* The source strings end up in the file "src-ui/messages.xlf".
* The translated strings need to be placed in the "src-ui/src/locale/" folder.
* In order to extract added or changed strings from the source files, call ``ng xi18n --ivy``.
Adding new languages requires adding the translated files in the "src-ui/src/locale/" folder and adjusting a couple files.
1. Adjust "src-ui/angular.json":
.. code:: json
"i18n": {
"sourceLocale": "en-US",
"locales": {
"de": "src/locale/messages.de.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"fr": "src/locale/messages.fr.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"language-code": "language-file"
}
}
2. Add the language to the available options in "src-ui/src/app/services/settings.service.ts":
.. code:: typescript
getLanguageOptions(): LanguageOption[] {
return [
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
{code: "de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
{code: "nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
{code: "fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"}
// Add your new language here
]
}
``dateInputFormat`` is a special string that defines the behavior of the date input fields and absolutely needs to contain "dd", "mm" and "yyyy".
3. Import and register the Angular data for this locale in "src-ui/src/app/app.module.ts":
.. code:: typescript
import localeDe from '@angular/common/locales/de';
registerLocaleData(localeDe)
Back end localization
---------------------
A majority of the strings that appear in the back end appear only when the admin is used. However,
some of these are still shown on the front end (such as error messages).
* The django application does localization according to the `django documentation <https://docs.djangoproject.com/en/3.1/topics/i18n/translation/>`_.
* The source language of the project is "en_US".
* Localization files end up in the folder "src/locale/".
* In order to extract strings from the application, call ``python3 manage.py makemessages -l en_US``. This is important after making changes to translatable strings.
* The message files need to be compiled for them to show up in the application. Call ``python3 manage.py compilemessages`` to do this. The generated files don't get
committed into git, since these are derived artifacts. The build pipeline takes care of executing this command.
Adding new languages requires adding the translated files in the "src/locale/" folder and adjusting the file "src/paperless/settings.py" to include the new language:
.. code:: python
LANGUAGES = [
("en-us", _("English (US)")),
("en-gb", _("English (GB)")),
("de", _("German")),
("nl-nl", _("Dutch")),
("fr", _("French")),
("pt-br", _("Portuguese (Brazil)")),
# Add language here.
]
Building the documentation
==========================
@@ -133,6 +269,23 @@ this is how you do it:
This will build the HTML documentation, and put the resulting files in the ``_build/html``
directory.
Building the Docker image
=========================
Building the docker image from source requires the following two steps:
1. Build the front end.
.. code:: shell-session
./compile-frontend.sh
2. Build the docker image.
.. code:: shell-session
docker build . -t <your-tag>
Extending Paperless
===================

View File

@@ -13,26 +13,35 @@ that works right for you based on recommendations from other Paperless users.
Physical scanners
=================
+---------+----------------+-----+-----+-----+----------------+
| Brand | Model | Supports | Recommended By |
+---------+----------------+-----+-----+-----+----------------+
| | | FTP | NFS | SMB | |
+=========+================+=====+=====+=====+================+
| Brother | `ADS-1500W`_ | yes | no | yes | `danielquinn`_ |
+---------+----------------+-----+-----+-----+----------------+
| Brother | `MFC-J6930DW`_ | yes | | | `ayounggun`_ |
+---------+----------------+-----+-----+-----+----------------+
| Brother | `MFC-J5910DW`_ | yes | | | `bmsleight`_ |
+---------+----------------+-----+-----+-----+----------------+
| Brother | `MFC-9142CDN`_ | yes | | yes | `REOLDEV`_ |
+---------+----------------+-----+-----+-----+----------------+
| Fujitsu | `ix500`_ | yes | | yes | `eonist`_ |
+---------+----------------+-----+-----+-----+----------------+
| Epson | `WF-7710DWF`_ | yes | | yes | `Skylinar`_ |
+---------+----------------+-----+-----+-----+----------------+
| Fujitsu | `S1300i`_ | yes | | yes | `jonaswinkler`_|
+---------+----------------+-----+-----+-----+----------------+
+---------+----------------+-----+-----+-----+------+----------------+
| Brand | Model | Supports | Recommended By |
+---------+----------------+-----+-----+-----+------+----------------+
| | | FTP | NFS | SMB | SMTP | |
+=========+================+=====+=====+=====+======+================+
| Brother | `ADS-1700W`_ | yes | no | yes | yes |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Brother | `ADS-1600W`_ | yes | no | yes | yes |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Brother | `ADS-1500W`_ | yes | no | yes | yes |`danielquinn`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Brother | `MFC-J6930DW`_ | yes | | | |`ayounggun`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Brother | `MFC-L5850DW`_ | yes | | | yes |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Brother | `MFC-J5910DW`_ | yes | | | |`bmsleight`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Brother | `MFC-9142CDN`_ | yes | | yes | |`REOLDEV`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Fujitsu | `ix500`_ | yes | | yes | |`eonist`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Epson | `WF-7710DWF`_ | yes | | yes | |`Skylinar`_ |
+---------+----------------+-----+-----+-----+------+----------------+
| Fujitsu | `S1300i`_ | yes | | yes | |`jonaswinkler`_ |
+---------+----------------+-----+-----+-----+------+----------------+
.. _MFC-L5850DW: https://www.brother-usa.com/products/mfcl5850dw
.. _ADS-1700W: https://www.brother-usa.com/products/ads1700w
.. _ADS-1600W: https://www.brother-usa.com/products/ads1600w
.. _ADS-1500W: https://www.brother.ca/en/p/ads1500w
.. _MFC-J6930DW: https://www.brother.ca/en/p/MFCJ6930DW
.. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw
@@ -41,6 +50,7 @@ Physical scanners
.. _WF-7710DWF: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf
.. _S1300i: https://www.fujitsu.com/global/products/computing/peripheral/scanners/soho/s1300i/
.. _danielquinn: https://github.com/danielquinn
.. _ayounggun: https://github.com/ayounggun
.. _bmsleight: https://github.com/bmsleight
@@ -48,25 +58,33 @@ Physical scanners
.. _REOLDEV: https://github.com/REOLDEV
.. _Skylinar: https://github.com/Skylinar
.. _jonaswinkler: https://github.com/jonaswinkler
.. _holzhannes: https://github.com/holzhannes
Mobile phone software
=====================
You can use your phone to "scan" documents. The regular camera app will work, but may have too low contrast for OCR to work well. Apps specifically for scanning are recommended.
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
| Name | OS | Supports | Recommended By |
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
| | | FTP | NFS | SMB | Email | WebDav | |
+===================+================+=====+=====+=====+=======+========+================+
| `Office Lens`_ | Android | ? | ? | ? | ? | ? | `jonaswinkler`_|
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
| `Genius Scan`_ | Android | yes | no | yes | yes | yes | `hannahswain`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| Name | OS | Supports | Recommended By |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| | | FTP | NFS | SMB | Email | WebDav | |
+===================+================+=====+=====+=====+=======+========+==================+
| `Office Lens`_ | Android | ? | ? | ? | ? | ? | `jonaswinkler`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| `Genius Scan`_ | Android | yes | no | yes | yes | yes | `hannahswain`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| `OpenScan`_ | Android | no | no | no | no | no | `benjaminfrank`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| `Quick Scan`_ | iOS | no | no | no | no | no | `holzhannes`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
On Android, you can use these applications in combination with one of the :ref:`Paperless-ng compatible apps <usage-mobile_upload>` to "Share" the documents produced by these scanner apps with paperless.
On Android, you can use these applications in combination with one of the :ref:`Paperless-ng compatible apps <usage-mobile_upload>` to "Share" the documents produced by these scanner apps with paperless. On iOS, you can share the scanned documents via iOS-Sharing to other mail, WebDav or FTP apps.
.. _Office Lens: https://play.google.com/store/apps/details?id=com.microsoft.office.officelens
.. _Genius Scan: https://play.google.com/store/apps/details?id=com.thegrizzlylabs.geniusscan.free
.. _Quick Scan: https://apps.apple.com/us/app/quickscan-scanner-text-ocr/id1513790291
.. _OpenScan: https://github.com/Ethereal-Developers-Inc/OpenScan
.. _hannahswain: https://github.com/hannahswain
.. _benjaminfrank: https://github.com/benjaminfrank

View File

@@ -284,6 +284,12 @@ writing. Windows is not and will never be supported.
* ``libmagic-dev`` for mime type detection
* ``mime-support`` for mime type detection
Use this list for your preferred package management:
.. code::
python3 python3-pip python3-dev imagemagick fonts-liberation optipng gnupg libpq-dev libmagic-dev mime-support
These dependencies are required for OCRmyPDF, which is used for text recognition.
* ``unpaper``
@@ -297,6 +303,12 @@ writing. Windows is not and will never be supported.
* ``tesseract-ocr`` >= 4.0.0 for OCR
* ``tesseract-ocr`` language packs (``tesseract-ocr-eng``, ``tesseract-ocr-deu``, etc)
Use this list for your preferred package management:
.. code::
unpaper ghostscript icc-profiles-free qpdf liblept5 libxml2 pngquant zlib1g tesseract-ocr
On Raspberry Pi, these libraries are required as well:
* ``libatlas-base-dev``

View File

@@ -255,6 +255,8 @@ Here are a couple examples of tags and types that you could use in your collecti
* A tag ``missing_metadata`` when you still need to add some metadata to a document, but can't
or don't want to do this right now.
.. _basic-usage_searching:
Searching
#########

View File

@@ -1,7 +1,5 @@
#!/bin/bash
set -e
ask() {
while true ; do
if [[ -z $3 ]] ; then
@@ -64,6 +62,21 @@ if [[ -z $(which docker-compose) ]] ; then
exit 1
fi
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
# If this fails, the user probably does not have permissions for Docker.
docker stats --no-stream 2>/dev/null 1>&2
if [ $? -ne 0 ] ; then
echo ""
echo "WARN: It look like the current user does not have Docker permissions."
echo "WARN: Use 'sudo usermod -aG docker $USER' to assign Docker permissions to the user."
echo ""
sleep 3
fi
default_time_zone=$(timedatectl show -p Timezone --value)
set -e
echo ""
echo "############################################"
echo "### Paperless-ng docker installation ###"
@@ -134,6 +147,15 @@ echo ""
ask "Port" "8000"
PORT=$ask_result
echo ""
echo "Paperless requires you to configure the current time zone correctly."
echo "Otherwise, the dates of your documents may appear off by one day,"
echo "depending on where you are on earth."
echo ""
ask "Current time zone" "$default_time_zone"
TIME_ZONE=$ask_result
echo ""
echo "Database backend: PostgreSQL and SQLite are available. Use PostgreSQL"
echo "if unsure. If you're running on a low-power device such as Raspberry"
@@ -269,6 +291,7 @@ DEFAULT_LANGUAGES="deu eng fra ita spa"
if [[ ! $USERMAP_GID == "1000" ]] ; then
echo "USERMAP_GID=$USERMAP_GID"
fi
echo "PAPERLESS_ZIME_ZONE=$TIME_ZONE"
echo "PAPERLESS_OCR_LANGUAGE=$OCR_LANGUAGE"
echo "PAPERLESS_SECRET_KEY=$SECRET_KEY"
if [[ ! " ${DEFAULT_LANGUAGES[@]} " =~ " ${OCR_LANGUAGE} " ]] ; then

View File

@@ -8,13 +8,13 @@
-i https://pypi.python.org/simple
--extra-index-url https://www.piwheels.org/simple
aioredis==1.3.1
arrow==0.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
arrow==1.0.3; python_version >= '3.6'
asgiref==3.3.1; python_version >= '3.5'
async-timeout==3.0.1; python_full_version >= '3.5.3'
attrs==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
autobahn==21.2.1; python_version >= '3.6'
autobahn==21.3.1; python_version >= '3.7'
automat==20.2.0
blessed==1.17.12
blessed==1.18.0
certifi==2020.12.5
cffi==1.14.5
channels-redis==3.2.0
@@ -24,58 +24,57 @@ click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2,
coloredlogs==15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
concurrent-log-handler==0.9.19
constantly==15.1.0
cryptography==3.3.2
cryptography==3.4.7
daphne==3.0.1; python_version >= '3.6'
dateparser==0.7.6
dateparser==1.0.0
django-cors-headers==3.7.0
django-extensions==3.1.1
django-filter==2.4.0
django-picklefield==3.0.1; python_version >= '3'
django-q==1.3.4
django==3.1.7
djangorestframework==3.12.2
djangorestframework==3.12.4
filelock==3.0.12
fuzzywuzzy[speedup]==0.18.0
gunicorn==20.0.4
gunicorn==20.1.0
h11==0.12.0; python_version >= '3.6'
hiredis==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
hiredis==2.0.0; python_version >= '3.6'
httptools==0.1.1
humanfriendly==9.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
hyperlink==21.0.0
idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
imap-tools==0.37.0
imap-tools==0.39.0
img2pdf==0.4.0
incremental==17.5.0
incremental==21.3.0
inotify-simple==1.3.5; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
inotifyrecursive==0.3.5
joblib==1.0.1; python_version >= '3.6'
langdetect==1.0.8
lxml==4.6.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
lxml==4.6.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
msgpack==1.0.2
numpy==1.19.5
ocrmypdf==11.6.2
pathvalidate==2.3.2
ocrmypdf==11.7.3
pathvalidate==2.4.0
pdfminer.six==20201018
pikepdf==2.5.2
pillow==8.1.0
pikepdf==2.10.0
pillow==8.2.0
pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
portalocker==2.2.1; python_version >= '3'
portalocker==2.3.0; python_version >= '3'
psycopg2-binary==2.8.6
pyasn1-modules==0.2.8
pyasn1==0.4.8
pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
pyhamcrest==2.0.2; python_version >= '3.5'
pyopenssl==20.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
python-dateutil==2.8.1
python-dotenv==0.15.0
python-gnupg==0.4.6
python-dotenv==0.16.0
python-gnupg==0.4.7
python-levenshtein==0.12.2
python-magic==0.4.22
pytz==2021.1
pyyaml==5.4.1
redis==3.5.3
regex==2020.11.13
reportlab==3.5.59
regex==2021.3.17
reportlab==3.5.66; python_version >= '2.7' and python_version < '4'
requests==2.25.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
scikit-learn==0.24.0
scipy==1.5.4
@@ -85,11 +84,11 @@ sortedcontainers==2.3.0
sqlparse==0.4.1; python_version >= '3.5'
threadpoolctl==2.1.0; python_version >= '3.5'
tika==1.24
tqdm==4.57.0
twisted[tls]==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
tqdm==4.59.0
twisted[tls]==21.2.0; python_full_version >= '3.5.4'
txaio==21.2.1; python_version >= '3.6'
tzlocal==2.1
urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
urllib3==1.26.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
uvicorn[standard]==0.13.4
uvloop==0.14.0
watchdog==1.0.2
@@ -98,4 +97,4 @@ wcwidth==0.2.5
websockets==8.1
whitenoise==5.2.0
whoosh==2.7.4
zope.interface==5.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
zope.interface==5.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'

View File

@@ -1,4 +1,4 @@
docker run -p 5432:5432 -v paperless_pgdata:/var/lib/postgresql/data -d postgres:13
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 thecodingmachine/gotenberg
docker run -p 9998:9998 -d apache/tika

View File

@@ -16,10 +16,16 @@
"i18n": {
"sourceLocale": "en-US",
"locales": {
"de": "src/locale/messages.de.xlf",
"de-DE": "src/locale/messages.de_DE.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"fr": "src/locale/messages.fr.xlf",
"en-GB": "src/locale/messages.en_GB.xlf"
"fr-FR": "src/locale/messages.fr_FR.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"pt-PT": "src/locale/messages.pt_PT.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"ro-RO": "src/locale/messages.ro_RO.xlf",
"ru-RU": "src/locale/messages.ru_RU.xlf",
"es-ES": "src/locale/messages.es_ES.xlf"
}
},
"architect": {
@@ -36,6 +42,7 @@
"aot": true,
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest", {
"glob": "pdf.worker.min.js",
@@ -100,7 +107,8 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
"browserTarget": "paperless-ui:build",
"ivy": true
}
},
"test": {
@@ -112,6 +120,7 @@
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest"
],

View File

@@ -48,21 +48,21 @@
<source>Documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">115</context>
<context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">136</context>
<context context-type="linenumber">138</context>
</context-group>
</trans-unit>
<trans-unit id="9ca82952a6bc860b5391d5975322d8af8ceddfa4" datatype="html">
@@ -146,77 +146,77 @@
<source>ASN</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">105</context>
<context context-type="linenumber">111</context>
</context-group>
</trans-unit>
<trans-unit id="7b5c6286aaded63fb279d6deb8aa8c704e085ced" datatype="html">
<source>Correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">117</context>
</context-group>
</trans-unit>
<trans-unit id="fdf7cbdc140d0aab0f0b6c06065a0fd448ed6a2e" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">117</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit id="2bd5919e8098513664a89d5b7b52d61e3063950f" datatype="html">
<source>Document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">123</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html">
<source>Created</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">129</context>
<context context-type="linenumber">135</context>
</context-group>
</trans-unit>
<trans-unit id="80e3b490720757978c99a7b5af3885faf202b955" datatype="html">
<source>Added</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">135</context>
<context context-type="linenumber">141</context>
</context-group>
</trans-unit>
<trans-unit id="9021887951960049161" datatype="html">
<source>Confirm delete</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">203</context>
<context context-type="linenumber">204</context>
</context-group>
</trans-unit>
<trans-unit id="5382975254277698192" datatype="html">
<source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">204</context>
<context context-type="linenumber">205</context>
</context-group>
</trans-unit>
<trans-unit id="6691075929777935948" datatype="html">
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">205</context>
<context context-type="linenumber">206</context>
</context-group>
</trans-unit>
<trans-unit id="719892092227206532" datatype="html">
<source>Delete document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">208</context>
</context-group>
</trans-unit>
<trans-unit id="1844801255494293730" datatype="html">
<source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">215</context>
</context-group>
</trans-unit>
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" datatype="html">
@@ -419,7 +419,7 @@
<source>Do you really want to delete the tag &quot;<x id="PH" equiv-text="object.name"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.ts</context>
<context context-type="linenumber">30</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
@@ -517,42 +517,35 @@
<source>Saved view &quot;<x id="PH" equiv-text="savedView.name"/>&quot; deleted.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="5647210819299459618" datatype="html">
<source>Settings saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">89</context>
</context-group>
</trans-unit>
<trans-unit id="6839066544204061364" datatype="html">
<source>Use system language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="7729897675462249787" datatype="html">
<source>Use date format of display language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="4912706592792948707" datatype="html">
<source>ISO 8601</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">97</context>
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="8488620293789898901" datatype="html">
<source>Error while storing settings on server: <x id="PH" equiv-text="JSON.stringify(error.error)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">114</context>
<context context-type="linenumber">117</context>
</context-group>
</trans-unit>
<trans-unit id="121cc5391cd2a5115bc2b3160379ee5b36cd7716" datatype="html">
@@ -573,14 +566,14 @@
<source>Notifications</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">115</context>
<context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit id="99dee94e92dbd9e21a008d4569f9719ed206ae37" datatype="html">
<source>Saved views</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">133</context>
<context context-type="linenumber">134</context>
</context-group>
</trans-unit>
<trans-unit id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13" datatype="html">
@@ -688,102 +681,109 @@
<context context-type="linenumber">98</context>
</context-group>
</trans-unit>
<trans-unit id="71bad20b37410c8972c9aa0f7c62996534b84339" datatype="html">
<source>Invert thumbnails in dark mode</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="3863a86cd9e69a61d143d3daf51df44203df4a82" datatype="html">
<source>Bulk editing</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">102</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="c0ac61661c6c326d6e0e00c231b95cf2ac0c6586" datatype="html">
<source>Show confirmation dialogs</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">106</context>
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit id="291bbe56ecbe945dcf05580a57d679fa7bd1e06a" datatype="html">
<source>Deleting documents will always ask for confirmation.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">106</context>
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit id="8cfddc13e04f5545ac63f419ef363505d6f78c2e" datatype="html">
<source>Apply on close</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">108</context>
</context-group>
</trans-unit>
<trans-unit id="8680abbea249ebe9c2fe35556559c8e1a9eb5841" datatype="html">
<source>Document processing</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">119</context>
</context-group>
</trans-unit>
<trans-unit id="2ad4d76b36341c589d94004ad2a213fd4d6f5ca0" datatype="html">
<source>Show notifications when new documents are detected</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">122</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit id="e775f4f7c40249d31426ae61a21616a0c9d8e84f" datatype="html">
<source>Show notifications when document processing completes successfully</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">123</context>
<context context-type="linenumber">124</context>
</context-group>
</trans-unit>
<trans-unit id="e3844dd174d8e817ddb551fae28f14ae80ca36b6" datatype="html">
<source>Show notifications when document processing fails</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">124</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit id="af113f7c9f7e13145c3461f61a1aedf12d57bd71" datatype="html">
<source>Suppress notifications on dashboard</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="e27bd3804d2936a6897e81c2e52e294490e5e5a8" datatype="html">
<source>This will suppress all messages about document processing status on the dashboard.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="8cb90334f5dfd7fc67205085f59381e2a334ccfc" datatype="html">
<source>Appears on</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">145</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="6717cf1acf04728fc2b7c39f6d3297f8ff15fde5" datatype="html">
<source>Show on dashboard</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">148</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="541bfc5b123b3f8867fd681eaceefb663a811973" datatype="html">
<source>Show in sidebar</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">152</context>
<context context-type="linenumber">153</context>
</context-group>
</trans-unit>
<trans-unit id="abba764a7a595d04dc8c3b26e04b3780d4fdb540" datatype="html">
<source>No saved views defined.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">163</context>
</context-group>
</trans-unit>
<trans-unit id="ef60a738a565f498b858e903e42bc5ffc3cc1299" datatype="html">
@@ -874,28 +874,28 @@
<source>Create new tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit id="5872175735754226507" datatype="html">
<source>Edit tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts</context>
<context context-type="linenumber">25</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="f2a30b4e1a89a8a0db0bd147b54d6626b9a9bc42" datatype="html">
<source>Inbox tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="5e2f1a4ea12a1b8606ee3f0548d0ba64bf266077" datatype="html">
<source>Inbox tags are automatically assigned to all consumed documents.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="6672809941092516947" datatype="html">
@@ -912,48 +912,6 @@
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="49c9ede51100b454f7841b24cd02355c6622bf44" datatype="html">
<source>Search results</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="31976d04f98e8a38098f66ac3a83ad33b576e5db" datatype="html">
<source>Invalid search query: <x id="INTERPOLATION" equiv-text="{{errorMessage}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit id="2abff6a01d9b342a5a14b7fb90309a95ce934f8e" datatype="html">
<source> Showing documents similar to <x id="START_LINK" equiv-text="&lt;a routerLink=&quot;/documents/{{more_like}}&quot;&gt;{{more_like_doc?.original_file_name}}"/><x id="INTERPOLATION" equiv-text="{{more_like_doc?.original_file_name}}&lt;/a&gt;"/><x id="CLOSE_LINK" equiv-text="&lt;/a&gt;"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">7</context>
</context-group>
</trans-unit>
<trans-unit id="6e0b0a1ea16f18f2fb1586c53d99d2f22e1aee2e" datatype="html">
<source>Search query: <x id="START_ITALIC_TEXT" equiv-text="&lt;i&gt;{{query}}"/><x id="INTERPOLATION" equiv-text="{{query}}&lt;/i&gt;"/><x id="CLOSE_ITALIC_TEXT" equiv-text="&lt;/i&gt;"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="afa760e48c97d64d19c1455d18b7834a2256e23f" datatype="html">
<source>Did you mean &quot;<x id="START_LINK" equiv-text="&lt;a [routerLink]=&quot;&quot; (click)=&quot;searchCorrectedQuery()&quot;&gt;{{correctedQuery}}"/><x id="INTERPOLATION" equiv-text="{{correctedQuery}}&lt;/a&gt;"/><x id="CLOSE_LINK" equiv-text="&lt;/a&gt;"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="fe6ced3fcc803bba5a2e6c1a067b9ce62542500e" datatype="html">
<source>{VAR_PLURAL, plural, =0 {No results} =1 {One result} other {<x id="INTERPOLATION"/> results}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="41147374f427980a9f1a8cd5e3f4b1666e6f2418" datatype="html">
<source>Paperless-ng</source>
<context-group purpose="location">
@@ -980,42 +938,42 @@
<source>Manage</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="408cb6073e60c5d966296a3207fc596adca75e01" datatype="html">
<source>Admin</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">149</context>
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5" datatype="html">
<source>Info</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">155</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7" datatype="html">
<source>Documentation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">167</context>
</context-group>
</trans-unit>
<trans-unit id="355a222236bc01b9a8cd3cb9ecf76891125aed69" datatype="html">
<source>GitHub</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">170</context>
<context context-type="linenumber">175</context>
</context-group>
</trans-unit>
<trans-unit id="ea3a452c5238897cabc5781308cceb2d37dcf258" datatype="html">
<source>Suggest an idea</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">176</context>
<context context-type="linenumber">181</context>
</context-group>
</trans-unit>
<trans-unit id="af665f8de8fabe306aaf27443957e69bcbbce63c" datatype="html">
@@ -1036,84 +994,126 @@
<source>Close all</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">101</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="5195932016807797291" datatype="html">
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find(c =&gt; c.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">29</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="8170755470576301659" datatype="html">
<source>Without correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">31</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="8705701325879965907" datatype="html">
<source>Type: <x id="PH" equiv-text="this.documentTypes.find(dt =&gt; dt.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">36</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="4362173610367509215" datatype="html">
<source>Without document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="8180755793012580465" datatype="html">
<source>Tag: <x id="PH" equiv-text="this.tags.find(t =&gt; t.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="6494566478302448576" datatype="html">
<source>Without any tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">46</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="6523384805359286307" datatype="html">
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="1872523635812236432" datatype="html">
<source>ASN: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">61</context>
</context-group>
</trans-unit>
<trans-unit id="5701618810648052610" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit id="3100631071441658964" datatype="html">
<source>Title &amp; content</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">86</context>
</context-group>
</trans-unit>
<trans-unit id="7517688192215738656" datatype="html">
<source>ASN</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit id="1010505078885609376" datatype="html">
<source>Advanced search</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">88</context>
</context-group>
</trans-unit>
<trans-unit id="2649431021108393503" datatype="html">
<source>More like</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">91</context>
</context-group>
</trans-unit>
<trans-unit id="02d184c288f567825a1fcbf83bcd3099a10853d5" datatype="html">
<source>Filter tags</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">12</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="4b089ca12c472cf0b46167bb5afe4b527b301bbc" datatype="html">
<source>Filter correspondents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">20</context>
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
<trans-unit id="0ad509732aaf702b7ea8c771c7809fa84bc85908" datatype="html">
<source>Filter document types</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="2d9d55f1b70142ff4597ba32179d16888fd9c6b2" datatype="html">
<source>Reset filters</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="7593728289020204896" datatype="html">
@@ -1135,28 +1135,28 @@
<source>Last 7 days</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/date-dropdown/date-dropdown.component.ts</context>
<context context-type="linenumber">24</context>
<context context-type="linenumber">34</context>
</context-group>
</trans-unit>
<trans-unit id="4463380307954693363" datatype="html">
<source>Last month</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/date-dropdown/date-dropdown.component.ts</context>
<context context-type="linenumber">25</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="8697368973702409683" datatype="html">
<source>Last 3 months</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/date-dropdown/date-dropdown.component.ts</context>
<context context-type="linenumber">26</context>
<context context-type="linenumber">36</context>
</context-group>
</trans-unit>
<trans-unit id="3566342898065860218" datatype="html">
<source>Last year</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/date-dropdown/date-dropdown.component.ts</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="be2add3a3d9e4e2556b8f9048a15a9c0f00bf1ad" datatype="html">
@@ -1170,7 +1170,7 @@
<source>Before</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/date-dropdown/date-dropdown.component.html</context>
<context context-type="linenumber">29</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit id="99ee4faa69cd2ea8e3678c1f557c0ff1f05aae46" datatype="html">
@@ -1184,14 +1184,7 @@
<source>View</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="849b42384616374df49bd8b3711ec159cb10b845" datatype="html">
<source>Created: <x id="INTERPOLATION" equiv-text="{{document.created | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="cd6f3fd48957e1fea6545c2b2defc7b2435ebfa8" datatype="html">
@@ -1212,14 +1205,28 @@
<source>Score:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">62</context>
<context context-type="linenumber">86</context>
</context-group>
</trans-unit>
<trans-unit id="2840db547019ce8c76b2cdbe3a1653c5b68b06af" datatype="html">
<source>View in browser</source>
<trans-unit id="727d980bba2b3e0b3d8705607f1208eef046479b" datatype="html">
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">40</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="0f5d856cb63c69fde44fbfc653ec0655f9040865" datatype="html">
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="a205126adef6251fc63305f1a6228d07bc2795a8" datatype="html">
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit id="7985804062689412812" datatype="html">
@@ -1605,6 +1612,20 @@
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="d6529debfc1613db22d6fa096ebfeb8a85fa739d" datatype="html">
<source>Invalid date.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
<context context-type="linenumber">14</context>
</context-group>
</trans-unit>
<trans-unit id="bd5ca454e336126f3eeb67417d9264696b5c852c" datatype="html">
<source>Searching document with asn <x id="INTERPOLATION" equiv-text="{{asn}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-asn/document-asn.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="2807800733729323332" datatype="html">
<source>Yes</source>
<context-group purpose="location">
@@ -1630,35 +1651,84 @@
<source>English (US)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">82</context>
<context context-type="linenumber">90</context>
</context-group>
</trans-unit>
<trans-unit id="6987083569809053351" datatype="html">
<source>English (GB)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">83</context>
<context context-type="linenumber">91</context>
</context-group>
</trans-unit>
<trans-unit id="1858110241312746425" datatype="html">
<source>German</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">84</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit id="3071065188816255493" datatype="html">
<source>Dutch</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">85</context>
<context context-type="linenumber">93</context>
</context-group>
</trans-unit>
<trans-unit id="7633754075223722162" datatype="html">
<source>French</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">86</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="153799456510623899" datatype="html">
<source>Portuguese</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="9184513005098760425" datatype="html">
<source>Portuguese (Brazil)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="2935232983274991580" datatype="html">
<source>Italian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">97</context>
</context-group>
</trans-unit>
<trans-unit id="8118856427047826368" datatype="html">
<source>Romanian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">98</context>
</context-group>
</trans-unit>
<trans-unit id="7137419789978325708" datatype="html">
<source>Russian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="5190825892106392539" datatype="html">
<source>Spanish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="4912706592792948707" datatype="html">
<source>ISO 8601</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="2119857572761283468" datatype="html">
@@ -1770,13 +1840,6 @@
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="7517688192215738656" datatype="html">
<source>ASN</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="2691296884221415710" datatype="html">
<source>Correspondent</source>
<context-group purpose="location">
@@ -1784,13 +1847,6 @@
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="5701618810648052610" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="5066119607229701477" datatype="html">
<source>Document type</source>
<context-group purpose="location">
@@ -1819,97 +1875,6 @@
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="2056433880533904076" datatype="html">
<source>Light blue</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">6</context>
</context-group>
</trans-unit>
<trans-unit id="4082253113407591781" datatype="html">
<source>Blue</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">7</context>
</context-group>
</trans-unit>
<trans-unit id="1143414876575720034" datatype="html">
<source>Light green</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">8</context>
</context-group>
</trans-unit>
<trans-unit id="119581980963263815" datatype="html">
<source>Green</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">9</context>
</context-group>
</trans-unit>
<trans-unit id="3250646524116252719" datatype="html">
<source>Light red</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit id="1628552745302385832" datatype="html">
<source>Red </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="5479028842846122610" datatype="html">
<source>Light orange</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="8598918991528773310" datatype="html">
<source>Orange</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="1789283185177957430" datatype="html">
<source>Light violet</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">14</context>
</context-group>
</trans-unit>
<trans-unit id="2682868487071320453" datatype="html">
<source>Violet</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit id="1449010446077321264" datatype="html">
<source>Brown</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="30300572504753589" datatype="html">
<source>Black</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="461048771215121187" datatype="html">
<source>Light grey</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="4561076822163447092" datatype="html">
<source>Create new item</source>
<context-group purpose="location">

View File

@@ -2035,6 +2035,11 @@
"to-fast-properties": "^2.0.0"
}
},
"@ctrl/tinycolor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
},
"@istanbuljs/schema": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
@@ -2055,9 +2060,9 @@
}
},
"@ng-bootstrap/ng-bootstrap": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-8.0.0.tgz",
"integrity": "sha512-v77Gfd8xHH+exq0WqIqVRlxbUEHdA/2+RUJenUP2IDTQN9E1rWl7O461/kosr+0XPuxPArHQJxhh/WsCYckcNg==",
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-8.0.4.tgz",
"integrity": "sha512-EdxTwOPOtlvfnwrglPniulmzdnXdXH3lTGaGAY1HrYRvdtGg6wicRvl+BvwVE/3Qik5NPkOWMVghUHpv3evIYg==",
"requires": {
"tslib": "^2.0.0"
}
@@ -7895,6 +7900,11 @@
"object-visit": "^1.0.0"
}
},
"material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -8333,6 +8343,16 @@
}
}
},
"ngx-color": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-6.2.0.tgz",
"integrity": "sha512-n04tcMnCpOgmI24egST94YwHmnSoAxK8O1T2t3nGrTwWbvw5XBRJvImNFnoNrriBXzc4Gx4hFehH5MU8CZxp1w==",
"requires": {
"@ctrl/tinycolor": "^3.1.6",
"material-colors": "^1.2.6",
"tslib": "^2.0.0"
}
},
"ngx-cookie-service": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-10.1.1.tgz",

View File

@@ -20,12 +20,13 @@
"@angular/platform-browser": "~10.1.5",
"@angular/platform-browser-dynamic": "~10.1.5",
"@angular/router": "~10.1.5",
"@ng-bootstrap/ng-bootstrap": "^8.0.0",
"@ng-bootstrap/ng-bootstrap": "^8.0.4",
"@ng-select/ng-select": "^5.0.9",
"bootstrap": "^4.5.0",
"file-saver": "^2.0.5",
"ng-bootstrap": "^1.6.3",
"ng2-pdf-viewer": "^6.3.2",
"ngx-color": "^6.2.0",
"ngx-cookie-service": "^10.1.1",
"ngx-file-drop": "^10.0.0",
"ngx-infinite-scroll": "^9.1.0",

View File

@@ -10,7 +10,7 @@ import { LogsComponent } from './components/manage/logs/logs.component';
import { SettingsComponent } from './components/manage/settings/settings.component';
import { TagListComponent } from './components/manage/tag-list/tag-list.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { SearchComponent } from './components/search/search.component';
import {DocumentAsnComponent} from "./components/document-asn/document-asn.component";
const routes: Routes = [
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
@@ -18,15 +18,15 @@ const routes: Routes = [
{path: 'dashboard', component: DashboardComponent },
{path: 'documents', component: DocumentListComponent },
{path: 'view/:id', component: DocumentListComponent },
{path: 'search', component: SearchComponent },
{path: 'documents/:id', component: DocumentDetailComponent },
{path: 'asn/:id', component: DocumentAsnComponent },
{path: 'tags', component: TagListComponent },
{path: 'documenttypes', component: DocumentTypeListComponent },
{path: 'correspondents', component: CorrespondentListComponent },
{path: 'logs', component: LogsComponent },
{path: 'settings', component: SettingsComponent },
]},
]},
{path: '404', component: NotFoundComponent},
{path: '**', redirectTo: '/404', pathMatch: 'full'}

View File

@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbDateAdapter, NgbDateParserFormatter, NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { DocumentListComponent } from './components/document-list/document-list.component';
import { DocumentDetailComponent } from './components/document-detail/document-detail.component';
@@ -21,8 +21,6 @@ import { CorrespondentEditDialogComponent } from './components/manage/correspond
import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
import { TagComponent } from './components/common/tag/tag.component';
import { SearchComponent } from './components/search/search.component';
import { ResultHighlightComponent } from './components/search/result-highlight/result-highlight.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,7 +37,6 @@ import { SelectComponent } from './components/common/input/select/select.compone
import { CheckComponent } from './components/common/input/check/check.component';
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { DateTimeComponent } from './components/common/input/date-time/date-time.component';
import { TagsComponent } from './components/common/input/tags/tags.component';
import { SortableDirective } from './directives/sortable.directive';
import { CookieService } from 'ngx-cookie-service';
@@ -60,16 +57,35 @@ import { NgSelectModule } from '@ng-select/ng-select';
import { NumberComponent } from './components/common/input/number/number.component';
import { SafePipe } from './pipes/safe.pipe';
import { CustomDatePipe } from './pipes/custom-date.pipe';
import { DateComponent } from './components/common/input/date/date.component';
import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter';
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter';
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor';
import { ColorSliderModule } from 'ngx-color/slider';
import { ColorComponent } from './components/common/input/color/color.component';
import localeFr from '@angular/common/locales/fr';
import localeNl from '@angular/common/locales/nl';
import localeDe from '@angular/common/locales/de';
import localePt from '@angular/common/locales/pt';
import localeIt from '@angular/common/locales/it';
import localeEnGb from '@angular/common/locales/en-GB';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeEs from '@angular/common/locales/es';
import { DocumentAsnComponent } from './components/document-asn/document-asn.component';
registerLocaleData(localeFr)
registerLocaleData(localeNl)
registerLocaleData(localeDe)
registerLocaleData(localePt, "pt-BR")
registerLocaleData(localePt, "pt-PT")
registerLocaleData(localeIt)
registerLocaleData(localeEnGb)
registerLocaleData(localeRo)
registerLocaleData(localeRu)
registerLocaleData(localeEs)
@NgModule({
declarations: [
@@ -88,8 +104,6 @@ registerLocaleData(localeEnGb)
TagEditDialogComponent,
DocumentTypeEditDialogComponent,
TagComponent,
SearchComponent,
ResultHighlightComponent,
PageHeaderComponent,
AppFrameComponent,
ToastsComponent,
@@ -104,7 +118,6 @@ registerLocaleData(localeEnGb)
SelectComponent,
CheckComponent,
SaveViewConfigDialogComponent,
DateTimeComponent,
TagsComponent,
SortableDirective,
SavedViewWidgetComponent,
@@ -120,7 +133,10 @@ registerLocaleData(localeEnGb)
SelectDialogComponent,
NumberComponent,
SafePipe,
CustomDatePipe
CustomDatePipe,
DateComponent,
ColorComponent,
DocumentAsnComponent
],
imports: [
BrowserModule,
@@ -132,7 +148,8 @@ registerLocaleData(localeEnGb)
NgxFileDropModule,
InfiniteScrollModule,
PdfViewerModule,
NgSelectModule
NgSelectModule,
ColorSliderModule
],
providers: [
DatePipe,
@@ -140,9 +157,15 @@ registerLocaleData(localeEnGb)
provide: HTTP_INTERCEPTORS,
useClass: CsrfInterceptor,
multi: true
},{
provide: HTTP_INTERCEPTORS,
useClass: ApiVersionInterceptor,
multi: true
},
FilterPipe,
DocumentTitlePipe
DocumentTitlePipe,
{provide: NgbDateAdapter, useClass: ISODateTimeAdapter},
{provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter}
],
bootstrap: [AppComponent]
})

View File

@@ -5,7 +5,7 @@
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand col-auto col-md-3 col-lg-2 mr-0 px-3 py-3 order-sm-0" routerLink="/dashboard">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1rem" class="mr-2" fill="currentColor">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="mr-2" fill="currentColor">
<path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/>
</svg>
<ng-container i18n="app title">Paperless-ng</ng-container>
@@ -31,7 +31,7 @@
</button>
<div ngbDropdownMenu class="dropdown-menu-right shadow mr-2" aria-labelledby="userDropdown">
<div *ngIf="displayName" class="d-sm-none">
<p class="small mb-0 px-3" i18n>Logged in as {{displayName}}</p>
<p class="small mb-0 px-3 text-muted" i18n>Logged in as {{displayName}}</p>
<div class="dropdown-divider"></div>
</div>
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()">
@@ -92,9 +92,14 @@
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
</svg>&nbsp;{{d.title | documentTitle}}
<span class="close bg-light" (click)="closeDocument(d); $event.preventDefault()">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>
</span>
</a>
</li>
<li class="nav-item w-100" *ngIf="openDocuments.length > 1">
<li class="nav-item w-100" *ngIf="openDocuments.length >= 1">
<a class="nav-link text-truncate" [routerLink]="" (click)="closeAll()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
@@ -170,7 +175,7 @@
</svg>&nbsp;<ng-container i18n>GitHub</ng-container>
</a>
<a class="nav-link-additional small text-muted ml-3" target="_blank" rel="noopener noreferrer" href="https://github.com/jonaswinkler/paperless-ng/discussions/categories/feature-requests" title="Suggest an idea">
<svg xmlns="http://www.w3.org/2000/svg" width=".9rem" height=".9rem" fill="currentColor" class="bi bi-lightbulb pr-1" viewBox="0 0 16 16">
<svg xmlns="http://www.w3.org/2000/svg" width="1.3em" height="1.3em" fill="currentColor" class="bi bi-lightbulb pr-1" viewBox="0 0 16 16">
<path d="M2 6a6 6 0 1 1 10.174 4.31c-.203.196-.359.4-.453.619l-.762 1.769A.5.5 0 0 1 10.5 13a.5.5 0 0 1 0 1 .5.5 0 0 1 0 1l-.224.447a1 1 0 0 1-.894.553H6.618a1 1 0 0 1-.894-.553L5.5 15a.5.5 0 0 1 0-1 .5.5 0 0 1 0-1 .5.5 0 0 1-.46-.302l-.761-1.77a1.964 1.964 0 0 0-.453-.618A5.984 5.984 0 0 1 2 6zm6-5a5 5 0 0 0-3.479 8.592c.263.254.514.564.676.941L5.83 12h4.342l.632-1.467c.162-.377.413-.687.676-.941A5 5 0 0 0 8 1z"/>
</svg>
<ng-container i18n>Suggest an idea</ng-container>

View File

@@ -62,16 +62,45 @@
flex-wrap: nowrap;
}
.nav-item .nav-link-additional {
margin-top: 0.2rem;
margin-left: 0.25rem;
padding-top: 0.5rem;
.nav-item {
position: relative;
svg {
margin-bottom: 2px;
&:hover .close {
display: block;
}
.close {
display: none;
position: absolute;
cursor: pointer;
opacity: 1;
top: 0;
padding: .25rem .3rem 0;
right: .4rem;
width: 1.8rem;
height: 100%;
svg {
opacity: 0.5;
}
&:hover svg {
opacity: 1;
}
}
.nav-link-additional {
margin-top: 0.2rem;
margin-left: 0.25rem;
padding-top: 0.5rem;
svg {
margin-bottom: 2px;
}
}
}
/*
* Navbar
*/

View File

@@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { from, Observable, Subscription } from 'rxjs';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { from, Observable, Subscription, BehaviorSubject } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
@@ -10,13 +10,15 @@ import { SearchService } from 'src/app/services/rest/search.service';
import { environment } from 'src/environments/environment';
import { DocumentDetailComponent } from '../document-detail/document-detail.component';
import { Meta } from '@angular/platform-browser';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type';
@Component({
selector: 'app-app-frame',
templateUrl: './app-frame.component.html',
styleUrls: ['./app-frame.component.scss']
})
export class AppFrameComponent implements OnInit, OnDestroy {
export class AppFrameComponent implements OnInit {
constructor (
public router: Router,
@@ -24,9 +26,10 @@ export class AppFrameComponent implements OnInit, OnDestroy {
private openDocumentsService: OpenDocumentsService,
private searchService: SearchService,
public savedViewService: SavedViewService,
private list: DocumentListViewService,
private meta: Meta
) {
}
versionString = `${environment.appTitle} ${environment.version}`
@@ -39,9 +42,9 @@ export class AppFrameComponent implements OnInit, OnDestroy {
searchField = new FormControl('')
openDocuments: PaperlessDocument[] = []
openDocumentsSubscription: Subscription
get openDocuments(): PaperlessDocument[] {
return this.openDocumentsService.getOpenDocuments()
}
searchAutoComplete = (text$: Observable<string>) =>
text$.pipe(
@@ -74,15 +77,27 @@ export class AppFrameComponent implements OnInit, OnDestroy {
search() {
this.closeMenu()
this.router.navigate(['search'], {queryParams: {query: this.searchField.value}})
this.list.quickFilter([{rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value}])
}
closeDocument(d: PaperlessDocument) {
this.closeMenu()
this.openDocumentsService.closeDocument(d)
let route = this.activatedRoute.snapshot
while (route.firstChild) {
route = route.firstChild
}
if (route.component == DocumentDetailComponent && route.params['id'] == d.id) {
this.router.navigate([""])
}
}
closeAll() {
this.closeMenu()
this.openDocumentsService.closeAll()
// TODO: is there a better way to do this?
let route = this.activatedRoute
let route = this.activatedRoute.snapshot
while (route.firstChild) {
route = route.firstChild
}
@@ -92,13 +107,6 @@ export class AppFrameComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.openDocuments = this.openDocumentsService.getOpenDocuments()
}
ngOnDestroy() {
if (this.openDocumentsSubscription) {
this.openDocumentsSubscription.unsubscribe()
}
}
get displayName() {

View File

@@ -20,8 +20,17 @@
</div>
<div class="input-group input-group-sm">
<input type="date" class="form-control" id="date_after" [(ngModel)]="dateAfter" (change)="onChangeDebounce()">
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()"
[(ngModel)]="dateAfter" ngbDatepicker #dateAfterPicker="ngbDatepicker">
<div class="input-group-append">
<button class="btn btn-outline-secondary" (click)="dateAfterPicker.toggle()" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
</svg>
</button>
</div>
</div>
</div>
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
@@ -36,8 +45,17 @@
</div>
<div class="input-group input-group-sm">
<input type="date" class="form-control" id="date_before" [(ngModel)]="dateBefore" (change)="onChangeDebounce()">
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()"
[(ngModel)]="dateBefore" ngbDatepicker #dateBeforePicker="ngbDatepicker">
<div class="input-group-append">
<button class="btn btn-outline-secondary" (click)="dateBeforePicker.toggle()" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,7 +1,10 @@
import { formatDate } from '@angular/common';
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { debounceTime } from 'rxjs/operators';
import { SettingsService } from 'src/app/services/settings.service';
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter';
export interface DateSelection {
before?: string
@@ -16,10 +19,17 @@ const LAST_YEAR = 3
@Component({
selector: 'app-date-dropdown',
templateUrl: './date-dropdown.component.html',
styleUrls: ['./date-dropdown.component.scss']
styleUrls: ['./date-dropdown.component.scss'],
providers: [
{provide: NgbDateAdapter, useClass: ISODateAdapter},
]
})
export class DateDropdownComponent implements OnInit, OnDestroy {
constructor(settings: SettingsService) {
this.datePlaceHolder = settings.getLocalizedDateInputFormat()
}
quickFilters = [
{id: LAST_7_DAYS, name: $localize`Last 7 days`},
{id: LAST_MONTH, name: $localize`Last month`},
@@ -27,6 +37,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
{id: LAST_YEAR, name: $localize`Last year`}
]
datePlaceHolder: string
@Input()
dateBefore: string

View File

@@ -0,0 +1,33 @@
<div class="form-group">
<label [for]="inputId">{{title}}</label>
<div class="input-group" [class.is-invalid]="error">
<div class="input-group-prepend">
<span class="input-group-text" [style.background-color]="value">&nbsp;&nbsp;&nbsp;</span>
</div>
<ng-template #popContent>
<div style="min-width: 200px;" class="pb-3">
<color-slider [color]="value" (onChangeComplete)="colorChanged($event.color.hex)"></color-slider>
</div>
</ng-template>
<input class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="randomize()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-5" viewBox="0 0 16 16">
<path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h10zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3H3z"/>
<path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm4-4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
</svg>
</button>
</div>
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
<div class="invalid-feedback">
{{error}}
</div>
</div>

View File

@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchComponent } from './search.component';
import { ColorComponent } from './color.component';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
describe('ColorComponent', () => {
let component: ColorComponent;
let fixture: ComponentFixture<ColorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SearchComponent ]
declarations: [ ColorComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
fixture = TestBed.createComponent(ColorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,30 @@
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { randomColor } from 'src/app/utils/color';
import { AbstractInputComponent } from '../abstract-input';
@Component({
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ColorComponent),
multi: true
}],
selector: 'app-input-color',
templateUrl: './color.component.html',
styleUrls: ['./color.component.scss']
})
export class ColorComponent extends AbstractInputComponent<string> {
constructor() {
super()
}
randomize() {
this.colorChanged(randomColor())
}
colorChanged(value) {
this.value = value
this.onChange(value)
}
}

View File

@@ -1,13 +0,0 @@
<div class="form-row">
<div class="form-group col">
<label for="created_date">{{titleDate}}</label>
<input type="date" class="form-control" id="created_date" [(ngModel)]="dateValue" (change)="dateOrTimeChanged()">
</div>
<div class="form-group col" *ngIf="titleTime">
<label for="created_time">{{titleTime}}</label>
<input type="time" class="form-control" id="created_time" [(ngModel)]="timeValue" (change)="dateOrTimeChanged()">
</div>
</div>
<!-- <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> -->

View File

@@ -1,61 +0,0 @@
import { formatDate } from '@angular/common';
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateTimeComponent),
multi: true
}],
selector: 'app-input-date-time',
templateUrl: './date-time.component.html',
styleUrls: ['./date-time.component.scss']
})
export class DateTimeComponent implements OnInit,ControlValueAccessor {
constructor() {
}
onChange = (newValue: any) => {};
onTouched = () => {};
writeValue(newValue: any): void {
this.dateValue = formatDate(newValue, 'yyyy-MM-dd', "en-US")
this.timeValue = formatDate(newValue, 'HH:mm:ss', 'en-US')
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
@Input()
titleDate: string = "Date"
@Input()
titleTime: string
@Input()
disabled: boolean = false
@Input()
hint: string
timeValue
dateValue
ngOnInit(): void {
}
dateOrTimeChanged() {
this.onChange(formatDate(this.dateValue + "T" + this.timeValue,"yyyy-MM-ddTHH:mm:ssZZZZZ", "en-us", "UTC"))
}
}

View File

@@ -0,0 +1,16 @@
<div class="form-group">
<label [for]="inputId">{{title}}</label>
<div class="input-group" [class.is-invalid]="error">
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" (dateSelect)="onChange(value)" (change)="onChange(value)"
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
<div class="input-group-append">
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
</svg>
</button>
</div>
</div>
<div class="invalid-feedback" i18n>Invalid date.</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
</div>

View File

@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DateTimeComponent } from './date-time.component';
import { DateComponent } from './date.component';
describe('DateTimeComponent', () => {
let component: DateTimeComponent;
let fixture: ComponentFixture<DateTimeComponent>;
describe('DateComponent', () => {
let component: DateComponent;
let fixture: ComponentFixture<DateComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DateTimeComponent ]
declarations: [ DateComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DateTimeComponent);
fixture = TestBed.createComponent(DateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,32 @@
import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NgbDateAdapter, NgbDateParserFormatter, NgbDatepickerContent } from '@ng-bootstrap/ng-bootstrap';
import { SettingsService } from 'src/app/services/settings.service';
import { v4 as uuidv4 } from 'uuid';
import { AbstractInputComponent } from '../abstract-input';
@Component({
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateComponent),
multi: true
}],
selector: 'app-input-date',
templateUrl: './date.component.html',
styleUrls: ['./date.component.scss']
})
export class DateComponent extends AbstractInputComponent<string> implements OnInit {
constructor(private settings: SettingsService) {
super()
}
ngOnInit(): void {
super.ngOnInit()
this.placeholder = this.settings.getLocalizedDateInputFormat()
}
placeholder: string
}

View File

@@ -1,33 +1,36 @@
<div class="form-group paperless-input-select">
<label [for]="inputId">{{title}}</label>
<div [class.input-group]="showPlusButton()">
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
[style.color]="textColor"
[style.background]="backgroundColor"
[clearable]="allowNull"
[items]="items"
bindLabel="name"
bindValue="id"
(change)="onChange(value)"
(blur)="onTouched()">
</ng-select>
<div *ngIf="showPlusButton()" class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="createNew.emit()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>
</button>
<div [class.input-group]="allowCreateNew">
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
[style.color]="textColor"
[style.background]="backgroundColor"
[clearable]="allowNull"
[items]="items"
[addTag]="allowCreateNew && addItemRef"
bindLabel="name"
bindValue="id"
(change)="onChange(value)"
(search)="onSearch($event)"
(focus)="clearLastSearchTerm()"
(clear)="clearLastSearchTerm()"
(blur)="onBlur()">
</ng-select>
<div *ngIf="allowCreateNew" class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="addItem()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>
</button>
</div>
</div>
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
<small *ngIf="getSuggestions().length > 0">
<span i18n>Suggestions:</span>&nbsp;
<ng-container *ngFor="let s of getSuggestions()">
<a (click)="value = s.id; onChange(value)" [routerLink]="">{{s.name}}</a>&nbsp;
</ng-container>
</small>
</div>

View File

@@ -16,6 +16,7 @@ export class SelectComponent extends AbstractInputComponent<number> {
constructor() {
super()
this.addItemRef = this.addItem.bind(this)
}
@Input()
@@ -34,9 +35,13 @@ export class SelectComponent extends AbstractInputComponent<number> {
suggestions: number[]
@Output()
createNew = new EventEmitter()
createNew = new EventEmitter<string>()
showPlusButton(): boolean {
public addItemRef: (name) => void
private _lastSearchTerm: string
get allowCreateNew(): boolean {
return this.createNew.observers.length > 0
}
@@ -48,4 +53,29 @@ export class SelectComponent extends AbstractInputComponent<number> {
}
}
addItem(name: string) {
if (name) this.createNew.next(name)
else this.createNew.next(this._lastSearchTerm)
this.clearLastSearchTerm()
}
clickNew() {
this.createNew.next(this._lastSearchTerm)
this.clearLastSearchTerm()
}
clearLastSearchTerm() {
this._lastSearchTerm = null
}
onSearch($event) {
this._lastSearchTerm = $event.term
}
onBlur() {
setTimeout(() => {
this.clearLastSearchTerm()
}, 3000);
}
}

View File

@@ -7,8 +7,13 @@
[closeOnSelect]="false"
[clearSearchOnAdd]="true"
[hideSelected]="true"
[addTag]="createTagRef"
addTagText="Add tag"
(change)="onChange(value)"
(blur)="onTouched()">
(search)="onSearch($event)"
(focus)="clearLastSearchTerm()"
(clear)="clearLastSearchTerm()"
(blur)="onBlur()">
<ng-template ng-label-tmp let-item="item">
<span class="tag-wrap tag-wrap-delete" (click)="removeTag(item.id)">
@@ -39,8 +44,8 @@
<ng-container *ngFor="let tag of getSuggestions()">
<a (click)="addTag(tag.id)" [routerLink]="">{{tag.name}}</a>&nbsp;
</ng-container>
</small>
</div>

View File

@@ -17,8 +17,9 @@ import { TagService } from 'src/app/services/rest/tag.service';
})
export class TagsComponent implements OnInit, ControlValueAccessor {
constructor(private tagService: TagService, private modalService: NgbModal) { }
constructor(private tagService: TagService, private modalService: NgbModal) {
this.createTagRef = this.createTag.bind(this)
}
onChange = (newValue: number[]) => {};
@@ -56,6 +57,10 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
tags: PaperlessTag[]
public createTagRef: (name) => void
private _lastSearchTerm: string
getTag(id) {
if (this.tags) {
return this.tags.find(tag => tag.id == id)
@@ -74,9 +79,11 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
}
}
createTag() {
createTag(name: string = null) {
var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (name) modal.componentInstance.object = { name: name }
else if (this._lastSearchTerm) modal.componentInstance.object = { name: this._lastSearchTerm }
modal.componentInstance.success.subscribe(newTag => {
this.tagService.listAll().subscribe(tags => {
this.tags = tags.results
@@ -99,4 +106,18 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
this.onChange(this.value)
}
clearLastSearchTerm() {
this._lastSearchTerm = null
}
onSearch($event) {
this._lastSearchTerm = $event.term
}
onBlur() {
setTimeout(() => {
this.clearLastSearchTerm()
}, 3000);
}
}

View File

@@ -1,2 +1,2 @@
<span *ngIf="!clickable" class="badge" [style.background]="getColour().value" [style.color]="getColour().textColor">{{tag.name}}</span>
<a [routerLink]="" [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="getColour().value" [style.color]="getColour().textColor">{{tag.name}}</a>
<span *ngIf="!clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
<a [routerLink]="" [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>

View File

@@ -1,5 +1,5 @@
import { Component, Input, OnInit } from '@angular/core';
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
import { PaperlessTag } from 'src/app/data/paperless-tag';
@Component({
selector: 'app-tag',
@@ -22,8 +22,4 @@ export class TagComponent implements OnInit {
ngOnInit(): void {
}
getColour() {
return TAG_COLOURS.find(c => c.id == this.tag.colour)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -23,7 +23,7 @@ form {
}
}
::ng-deep .progress {
.progress {
position: absolute;
top: 0;
right: 0;

View File

@@ -0,0 +1 @@
<p i18n>Searching document with asn {{asn}}</p>

View File

@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResultHighlightComponent } from './result-highlight.component';
import { DocumentAsnComponent } from './document-asn.component';
describe('ResultHighlightComponent', () => {
let component: ResultHighlightComponent;
let fixture: ComponentFixture<ResultHighlightComponent>;
describe('DocumentASNComponentComponent', () => {
let component: DocumentAsnComponent;
let fixture: ComponentFixture<DocumentAsnComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ResultHighlightComponent ]
declarations: [ DocumentAsnComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ResultHighlightComponent);
fixture = TestBed.createComponent(DocumentAsnComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import {DocumentService} from "../../services/rest/document.service";
import {ActivatedRoute, Router} from "@angular/router";
import {FILTER_ASN} from "../../data/filter-rule-type";
@Component({
selector: 'app-document-asncomponent',
templateUrl: './document-asn.component.html',
styleUrls: ['./document-asn.component.scss']
})
export class DocumentAsnComponent implements OnInit {
asn: string
constructor(
private documentsService: DocumentService,
private route: ActivatedRoute,
private router: Router) { }
ngOnInit(): void {
this.route.paramMap.subscribe(paramMap => {
this.asn = paramMap.get('id');
this.documentsService.listAllFilteredIds([{rule_type: FILTER_ASN, value: this.asn}]).subscribe(documentId => {
if (documentId.length == 1) {
this.router.navigate(['documents', documentId[0]])
} else {
this.router.navigate(['404'])
}
})
})
}
}

View File

@@ -58,11 +58,11 @@
<app-input-text #inputTitle i18n-title title="Title" formControlName="title" [error]="error?.title"></app-input-text>
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
<app-input-date-time i18n-titleDate titleDate="Date created" formControlName="created"></app-input-date-time>
<app-input-date i18n-title title="Date created" formControlName="created" [error]="error?.created"></app-input-date>
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
(createNew)="createCorrespondent()" [suggestions]="suggestions?.correspondents"></app-input-select>
(createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents"></app-input-select>
<app-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true"
(createNew)="createDocumentType()" [suggestions]="suggestions?.document_types"></app-input-select>
(createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types"></app-input-select>
<app-input-tags formControlName="tags" [suggestions]="suggestions?.tags"></app-input-tags>
</ng-template>

View File

@@ -20,6 +20,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { TextComponent } from '../common/input/text/text.component';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
@Component({
selector: 'app-document-detail',
@@ -127,9 +128,10 @@ export class DocumentDetailComponent implements OnInit {
this.documentForm.patchValue(doc)
}
createDocumentType() {
createDocumentType(newName: string) {
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName }
modal.componentInstance.success.subscribe(newDocumentType => {
this.documentTypeService.listAll().subscribe(documentTypes => {
this.documentTypes = documentTypes.results
@@ -138,9 +140,10 @@ export class DocumentDetailComponent implements OnInit {
})
}
createCorrespondent() {
createCorrespondent(newName: string) {
var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName }
modal.componentInstance.success.subscribe(newCorrespondent => {
this.correspondentService.listAll().subscribe(correspondents => {
this.correspondents = correspondents.results
@@ -219,7 +222,7 @@ export class DocumentDetailComponent implements OnInit {
}
moreLike() {
this.router.navigate(["search"], {queryParams: {more_like:this.document.id}})
this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}])
}
hasNext() {

View File

@@ -1,7 +1,7 @@
<div class="card mb-3 shadow-sm" [class.card-selected]="selected" [class.document-card]="selectable">
<div class="card mb-3 shadow-sm" [class.card-selected]="selected" [class.document-card]="selectable" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()">
<div class="row no-gutters">
<div class="col-md-2 d-none d-lg-block doc-img-background rounded-left" [class.doc-img-background-selected]="selected" (click)="this.toggleSelected.emit($event)">
<img [src]="getThumbUrl()" class="card-img doc-img border-right rounded-left">
<img [src]="getThumbUrl()" class="card-img doc-img border-right rounded-left" [class.inverted]="getIsThumbInverted()">
<div style="top: 0; left: 0" class="position-absolute border-right border-bottom bg-light p-1" [class.document-card-check]="!selected">
<div class="custom-control custom-checkbox">
@@ -23,17 +23,16 @@
{{document.title | documentTitle}}
<app-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle *ngFor="let t of document.tags$ | async" class="ml-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></app-tag>
</h5>
<h5 class="card-title" *ngIf="document.archive_serial_number">#{{document.archive_serial_number}}</h5>
</div>
<p class="card-text">
<app-result-highlight *ngIf="getDetailsAsHighlight()" class="result-content" [highlights]="getDetailsAsHighlight()"></app-result-highlight>
<span *ngIf="getDetailsAsString()" class="result-content">{{getDetailsAsString()}}</span>
<span *ngIf="document.__search_hit__" [innerHtml]="document.__search_hit__.highlights"></span>
<span *ngIf="!document.__search_hit__" class="result-content">{{contentTrimmed}}</span>
</p>
<div class="d-flex flex-column flex-md-row align-items-md-center">
<div class="btn-group">
<a routerLink="/search" [queryParams]="{'more_like': document.id}" class="btn btn-sm btn-outline-secondary" *ngIf="moreLikeThis">
<a class="btn btn-sm btn-outline-secondary" (click)="clickMoreLike.emit()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-three-dots" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
</svg>&nbsp;<span class="d-block d-md-inline" i18n>More like this</span>
@@ -43,28 +42,51 @@
<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>&nbsp;<span class="d-block d-md-inline" i18n>Edit</span>
</a>
<a class="btn btn-sm btn-outline-secondary" [href]="getPreviewUrl()">
<a class="btn btn-sm btn-outline-secondary" [href]="previewUrl"
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
autoClose="true" popoverClass="shadow" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
</svg>&nbsp;<span class="d-block d-md-inline" i18n>View</span>
</a>
<ng-template #previewContent>
<object [data]="previewUrl | safe" class="preview" width="100%"></object>
</ng-template>
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
<path fill-rule="evenodd" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
</svg>&nbsp;<span class="d-block d-md-inline" i18n>Download</span>
</a>
</div>
<div *ngIf="searchScore" class="d-flex align-items-center ml-md-auto mt-2 mt-md-0">
<small class="text-muted" i18n>Score:</small>
<ngb-progressbar [type]="searchScoreClass" [value]="searchScore" class="search-score-bar mx-2" [max]="1"></ngb-progressbar>
<div class="list-group list-group-horizontal border-0 card-info ml-md-auto mt-2 mt-md-0">
<button *ngIf="document.document_type" type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 mr-2" title="Filter by document type"
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<svg class="metadata-icon mr-2 text-muted bi bi-file-earmark" viewBox="0 0 16 16" fill="currentColor">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
</svg>
<small>{{(document.document_type$ | async)?.name}}</small>
</button>
<div *ngIf="document.archive_serial_number" class="list-group-item mr-2 bg-light text-dark p-1 border-0">
<svg class="metadata-icon mr-2 text-muted bi bi-upc-scan" viewBox="0 0 16 16" fill="currentColor">
<path d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5zM3 4.5a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7zm3 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7z"/>
</svg>
<small>#{{document.archive_serial_number}}</small>
</div>
<div class="list-group-item bg-light text-dark p-1 border-0" ngbTooltip="Added:&nbsp;{{document.added | customDate:'shortDate'}} Created:&nbsp;{{document.created | customDate:'shortDate'}}">
<svg class="metadata-icon mr-2 text-muted bi bi-calendar-event" viewBox="0 0 16 16" fill="currentColor">
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
</svg>
<small>{{document.created | customDate:'mediumDate'}}</small>
</div>
<div *ngIf="document.__search_hit__" class="list-group-item bg-light text-dark border-0 d-flex search-score">
<small class="text-muted" i18n>Score:</small>
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
</div>
</div>
<small class="text-muted" [class.ml-auto]="!searchScore" i18n>Created: {{document.created | customDate}}</small>
</div>
</div>

View File

@@ -37,3 +37,31 @@
.doc-img-background-selected {
background-color: $primaryFaded;
}
.card-info {
line-height: 1;
button {
line-height: 1;
&:hover,
&:focus {
background-color: transparent !important;
}
}
.metadata-icon {
width: 0.9rem;
height: 0.9rem;
padding: 0.05rem;
}
.search-score {
padding-top: 0.35rem !important;
}
}
span ::ng-deep .match {
color: black;
background-color: rgb(255, 211, 66);
}

View File

@@ -1,16 +1,20 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { DocumentService } from 'src/app/services/rest/document.service';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
@Component({
selector: 'app-document-card-large',
templateUrl: './document-card-large.component.html',
styleUrls: ['./document-card-large.component.scss']
styleUrls: ['./document-card-large.component.scss', '../popover-preview/popover-preview.scss']
})
export class DocumentCardLargeComponent implements OnInit {
constructor(private documentService: DocumentService, private sanitizer: DomSanitizer) { }
constructor(private documentService: DocumentService, private sanitizer: DomSanitizer, private settingsService: SettingsService) { }
@Input()
selected = false
@@ -22,48 +26,43 @@ export class DocumentCardLargeComponent implements OnInit {
return this.toggleSelected.observers.length > 0
}
@Input()
moreLikeThis: boolean = false
@Input()
document: PaperlessDocument
@Input()
details: any
@Output()
clickTag = new EventEmitter<number>()
@Output()
clickCorrespondent = new EventEmitter<number>()
@Input()
searchScore: number
@Output()
clickDocumentType = new EventEmitter<number>()
@Output()
clickMoreLike= new EventEmitter()
@ViewChild('popover') popover: NgbPopover
mouseOnPreview = false
popoverHidden = true
get searchScoreClass() {
if (this.searchScore > 0.7) {
return "success"
} else if (this.searchScore > 0.3) {
return "warning"
} else {
return "danger"
if (this.document.__search_hit__) {
if (this.document.__search_hit__.score > 0.7) {
return "success"
} else if (this.document.__search_hit__.score > 0.3) {
return "warning"
} else {
return "danger"
}
}
}
ngOnInit(): void {
}
getDetailsAsString() {
if (typeof this.details === 'string') {
return this.details.substring(0, 500)
}
}
getDetailsAsHighlight() {
//TODO: this is not an exact typecheck, can we do better
if (this.details instanceof Array) {
return this.details
}
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}
getThumbUrl() {
@@ -74,7 +73,36 @@ export class DocumentCardLargeComponent implements OnInit {
return this.documentService.getDownloadUrl(this.document.id)
}
getPreviewUrl() {
get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id)
}
mouseEnterPreview() {
this.mouseOnPreview = true
if (!this.popover.isOpen()) {
// we're going to open but hide to pre-load content during hover delay
this.popover.open()
this.popoverHidden = true
setTimeout(() => {
if (this.mouseOnPreview) {
// show popover
this.popoverHidden = false
} else {
this.popover.close()
}
}, 600);
}
}
mouseLeavePreview() {
this.mouseOnPreview = false
}
mouseLeaveCard() {
this.popover.close()
}
get contentTrimmed() {
return this.document.content.substr(0, 500)
}
}

View File

@@ -1,7 +1,7 @@
<div class="col p-2 h-100">
<div class="card h-100 shadow-sm document-card" [class.card-selected]="selected">
<div class="card h-100 shadow-sm document-card" [class.card-selected]="selected" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()">
<div class="border-bottom doc-img-container" [class.doc-img-background-selected]="selected" (click)="this.toggleSelected.emit($event)">
<img class="card-img doc-img rounded-top" [src]="getThumbUrl()">
<img class="card-img doc-img rounded-top" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()">
<div class="border-right border-bottom bg-light p-1 rounded document-card-check">
<div class="custom-control custom-checkbox">
@@ -25,24 +25,60 @@
<ng-container *ngIf="document.correspondent">
<a [routerLink]="" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="font-weight-bold">{{(document.correspondent$ | async)?.name}}</a>:
</ng-container>
{{document.title | documentTitle}} <span *ngIf="document.archive_serial_number">(#{{document.archive_serial_number}})</span>
{{document.title | documentTitle}}
</p>
</div>
<div class="card-footer">
<div class="card-footer pt-0 pb-2 px-2">
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
<button *ngIf="document.document_type" type="button" class="list-group-item list-group-item-action bg-transparent pl-0 p-1 border-0" title="Filter by document type"
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<svg class="metadata-icon mr-2 text-muted bi bi-file-earmark" viewBox="0 0 16 16" fill="currentColor">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
</svg>
<small>{{(document.document_type$ | async)?.name}}</small>
</button>
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
<ng-template #dateTooltip>
<div class="d-flex flex-column">
<span i18n>Created: {{ document.created | customDate}}</span>
<span i18n>Added: {{ document.added | customDate}}</span>
<span i18n>Modified: {{ document.modified | customDate}}</span>
</div>
</ng-template>
<div class="d-flex justify-content-between align-items-center mx-n2">
<div class="btn-group">
<div class="pl-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
<svg class="metadata-icon mr-2 text-muted bi bi-calendar-event" viewBox="0 0 16 16" fill="currentColor">
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
</svg>
<small>{{document.created | customDate:'mediumDate'}}</small>
</div>
<div *ngIf="document.archive_serial_number" class="pl-0 p-1">
<svg class="metadata-icon mr-2 text-muted bi bi-upc-scan" viewBox="0 0 16 16" fill="currentColor">
<path d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5zM3 4.5a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7zm3 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7z"/>
</svg>
<small>#{{document.archive_serial_number}}</small>
</div>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group w-100">
<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>
</a>
<a [href]="getPreviewUrl()" class="btn btn-sm btn-outline-secondary" title="View in browser" i18n-title>
<a [href]="previewUrl" target="_blank" class="btn btn-sm btn-outline-secondary"
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
autoClose="true" popoverClass="shadow" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
</svg>
</a>
<ng-template #previewContent>
<object [data]="previewUrl | safe" class="preview" width="100%"></object>
</ng-template>
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" (click)="$event.stopPropagation()" i18n-title>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
@@ -50,9 +86,7 @@
</svg>
</a>
</div>
<small class="text-muted pl-1">{{document.created | customDate:'shortDate'}}</small>
</div>
</div>
</div>
</div>

View File

@@ -1,9 +1,13 @@
@import "/src/theme";
.card-text {
font-size: 90%;
}
.doc-img {
object-fit: cover;
object-position: top left;
height: 200px;
height: 175px;
mix-blend-mode: multiply;
}
@@ -34,3 +38,32 @@
.doc-img-background-selected {
background-color: $primaryFaded;
}
.card-info {
line-height: 1;
button {
line-height: 1;
&:hover,
&:focus {
background-color: transparent !important;
color: $primary;
}
}
.metadata-icon {
width: 0.9rem;
height: 0.9rem;
padding: 0.05rem;
}
}
.card-footer .btn {
padding-top: .10rem;
}
::ng-deep .tooltip-inner {
text-align: left !important;
font-size: 90%;
}

View File

@@ -1,20 +1,22 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { map } from 'rxjs/operators';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { DocumentService } from 'src/app/services/rest/document.service';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-document-card-small',
templateUrl: './document-card-small.component.html',
styleUrls: ['./document-card-small.component.scss']
styleUrls: ['./document-card-small.component.scss', '../popover-preview/popover-preview.scss']
})
export class DocumentCardSmallComponent implements OnInit {
constructor(private documentService: DocumentService) { }
constructor(private documentService: DocumentService, private settingsService: SettingsService) { }
@Input()
selected = false
@Output()
toggleSelected = new EventEmitter()
@@ -27,11 +29,23 @@ export class DocumentCardSmallComponent implements OnInit {
@Output()
clickCorrespondent = new EventEmitter<number>()
@Output()
clickDocumentType = new EventEmitter<number>()
moreTags: number = null
@ViewChild('popover') popover: NgbPopover
mouseOnPreview = false
popoverHidden = true
ngOnInit(): void {
}
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}
getThumbUrl() {
return this.documentService.getThumbUrl(this.document.id)
}
@@ -40,7 +54,7 @@ export class DocumentCardSmallComponent implements OnInit {
return this.documentService.getDownloadUrl(this.document.id)
}
getPreviewUrl() {
get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id)
}
@@ -57,4 +71,28 @@ export class DocumentCardSmallComponent implements OnInit {
)
}
mouseEnterPreview() {
this.mouseOnPreview = true
if (!this.popover.isOpen()) {
// we're going to open but hide to pre-load content during hover delay
this.popover.open()
this.popoverHidden = true
setTimeout(() => {
if (this.mouseOnPreview) {
// show popover
this.popoverHidden = false
} else {
this.popover.close()
}
}, 600);
}
}
mouseLeavePreview() {
this.mouseOnPreview = false
}
mouseLeaveCard() {
this.popover.close()
}
}

View File

@@ -76,7 +76,7 @@
</app-page-header>
<div class="w-100 mb-2 mb-sm-4">
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [rulesModified]="filterRulesModified" (filterRulesChange)="rulesChanged()" (reset)="resetFilters()" #filterEditor></app-filter-editor>
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor>
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
</div>
@@ -89,86 +89,95 @@
[rotate]="true" aria-label="Default pagination"></ngb-pagination>
</div>
<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" [details]="d.content" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)">
</app-document-card-large>
</div>
<ng-container *ngIf="list.error ; else documentListNoError">
<div class="alert alert-danger" role="alert">Error while loading documents: {{list.error}}</div>
</ng-container>
<table class="table table-sm border shadow-sm" *ngIf="displayMode == 'details'">
<thead>
<th></th>
<th class="d-none d-lg-table-cell"
sortable="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"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Correspondent</th>
<th
sortable="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"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Document type</th>
<th
sortable="created"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Created</th>
<th class="d-none d-xl-table-cell"
sortable="added"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Added</th>
</thead>
<tbody>
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" (click)="toggleSelected(d, $event)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
<td>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event)">
<label class="custom-control-label" for="docCheck{{d.id}}"></label>
</div>
</td>
<td class="d-none d-lg-table-cell">
{{d.archive_serial_number}}
</td>
<td class="d-none d-md-table-cell">
<ng-container *ngIf="d.correspondent">
<a [routerLink]="" (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent">{{(d.correspondent$ | async)?.name}}</a>
</ng-container>
</td>
<td>
<a routerLink="/documents/{{d.id}}" title="Edit document" style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
</td>
<td class="d-none d-xl-table-cell">
<ng-container *ngIf="d.document_type">
<a [routerLink]="" (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type">{{(d.document_type$ | async)?.name}}</a>
</ng-container>
</td>
<td>
{{d.created | customDate}}
</td>
<td class="d-none d-xl-table-cell">
{{d.added | customDate}}
</td>
</tr>
</tbody>
</table>
<ng-template #documentListNoError>
<div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"></app-document-card-small>
</div>
<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)" (clickMoreLike)="clickMoreLike(d.id)">
</app-document-card-large>
</div>
<table class="table table-sm border shadow-sm" *ngIf="displayMode == 'details'">
<thead>
<th></th>
<th class="d-none d-lg-table-cell"
sortable="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"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Correspondent</th>
<th
sortable="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"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Document type</th>
<th
sortable="created"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Created</th>
<th class="d-none d-xl-table-cell"
sortable="added"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Added</th>
</thead>
<tbody>
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" (click)="toggleSelected(d, $event)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
<td>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event)">
<label class="custom-control-label" for="docCheck{{d.id}}"></label>
</div>
</td>
<td class="d-none d-lg-table-cell">
{{d.archive_serial_number}}
</td>
<td class="d-none d-md-table-cell">
<ng-container *ngIf="d.correspondent">
<a [routerLink]="" (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent">{{(d.correspondent$ | async)?.name}}</a>
</ng-container>
</td>
<td>
<a routerLink="/documents/{{d.id}}" title="Edit document" style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
</td>
<td class="d-none d-xl-table-cell">
<ng-container *ngIf="d.document_type">
<a [routerLink]="" (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type">{{(d.document_type$ | async)?.name}}</a>
</ng-container>
</td>
<td>
{{d.created | customDate}}
</td>
<td class="d-none d-xl-table-cell">
{{d.added | customDate}}
</td>
</tr>
</tbody>
</table>
<div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
</div>
</ng-template>

View File

@@ -2,6 +2,8 @@ import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subscription } from 'rxjs';
import { FilterRule } from 'src/app/data/filter-rule';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive';
@@ -37,7 +39,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
displayMode = 'smallCards' // largeCards, smallCards, details
filterRulesModified: boolean = false
unmodifiedFilterRules: FilterRule[] = []
private consumptionFinishedSubscription: Subscription
@@ -81,12 +83,12 @@ export class DocumentListComponent implements OnInit, OnDestroy {
}
this.list.activateSavedView(view)
this.list.reload()
this.rulesChanged()
this.unmodifiedFilterRules = view.filter_rules
})
} else {
this.list.activateSavedView(null)
this.list.reload()
this.rulesChanged()
this.unmodifiedFilterRules = []
}
})
}
@@ -100,7 +102,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
loadViewConfig(view: PaperlessSavedView) {
this.list.loadSavedView(view)
this.list.reload()
this.rulesChanged()
}
saveViewConfig() {
@@ -113,6 +114,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
}
this.savedViewService.patch(savedView).subscribe(result => {
this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`)
this.unmodifiedFilterRules = this.list.filterRules
})
}
}
@@ -141,46 +143,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
})
}
resetFilters(): void {
this.filterRulesModified = false
if (this.list.activeSavedViewId) {
this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(viewUntouched => {
this.list.filterRules = viewUntouched.filter_rules
this.list.reload()
})
} else {
this.list.filterRules = []
this.list.reload()
}
}
rulesChanged() {
let modified = false
if (this.list.activeSavedViewId == null) {
modified = this.list.filterRules.length > 0 // documents list is modified if it has any filters
} else {
// compare savedView current filters vs original
this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(view => {
let filterRulesInitial = view.filter_rules
if (this.list.filterRules.length !== filterRulesInitial.length) modified = true
else {
modified = this.list.filterRules.some(rule => {
return (filterRulesInitial.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined)
})
if (!modified) {
// only check other direction if we havent already determined is modified
modified = filterRulesInitial.some(rule => {
this.list.filterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined
})
}
}
})
}
this.filterRulesModified = modified
}
toggleSelected(document: PaperlessDocument, event: MouseEvent): void {
if (!event.shiftKey) this.list.toggleSelected(document)
else this.list.selectRangeTo(document)
@@ -207,6 +169,10 @@ export class DocumentListComponent implements OnInit, OnDestroy {
})
}
clickMoreLike(documentID: number) {
this.list.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString()}])
}
trackByDocumentId(index, item: PaperlessDocument) {
return item.id
}

View File

@@ -1,8 +1,15 @@
<div class="row">
<div class="col mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center">
<label class="text-muted mr-2 mb-0" i18n>Filter by:</label>
<input class="form-control form-control-sm flex-fill w-auto" type="text" [(ngModel)]="titleFilter" placeholder="Title" i18n-placeholder>
<div class="input-group input-group-sm flex-fill w-auto">
<div class="input-group-prepend" ngbDropdown>
<button class="btn 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>
</div>
</div>
<input #textFilterInput class="form-control form-control-sm" type="text" [(ngModel)]="textFilter" [readonly]="textFilterTarget == 'fulltext-morelike'">
</div>
</div>
</div>
<div class="w-100 d-xl-none"></div>

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy, ViewChild, ElementRef } 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';
@@ -8,9 +8,17 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service
import { TagService } from 'src/app/services/rest/tag.service';
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
import { FilterRule } from 'src/app/data/filter-rule';
import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_ANY_TAG, FILTER_HAS_TAG, FILTER_TITLE } from 'src/app/data/filter-rule-type';
import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_ASN, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY, FILTER_HAS_ANY_TAG, FILTER_HAS_TAG, FILTER_TITLE, FILTER_TITLE_CONTENT } from 'src/app/data/filter-rule-type';
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
import { DocumentService } from 'src/app/services/rest/document.service';
import { PaperlessDocument } from 'src/app/data/paperless-document';
const TEXT_FILTER_TARGET_TITLE = "title"
const TEXT_FILTER_TARGET_TITLE_CONTENT = "title-content"
const TEXT_FILTER_TARGET_ASN = "asn"
const TEXT_FILTER_TARGET_FULLTEXT_QUERY = "fulltext-query"
const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = "fulltext-morelike"
@Component({
selector: 'app-filter-editor',
@@ -48,6 +56,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
case FILTER_TITLE:
return $localize`Title: ${rule.value}`
case FILTER_ASN:
return $localize`ASN: ${rule.value}`
}
}
@@ -57,14 +68,40 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
constructor(
private documentTypeService: DocumentTypeService,
private tagService: TagService,
private correspondentService: CorrespondentService
private correspondentService: CorrespondentService,
private documentService: DocumentService
) { }
@ViewChild("textFilterInput")
textFilterInput: ElementRef
tags: PaperlessTag[] = []
correspondents: PaperlessCorrespondent[] = []
documentTypes: PaperlessDocumentType[] = []
_titleFilter = ""
_textFilter = ""
_moreLikeId: number
_moreLikeDoc: PaperlessDocument
get textFilterTargets() {
let targets = [
{id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`},
{id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`},
{id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN`},
{id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, name: $localize`Advanced search`}
]
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
targets.push({id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE, name: $localize`More like`})
}
return targets
}
textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
get textFilterTargetName() {
return this.textFilterTargets.find(t => t.id == this.textFilterTarget)?.name
}
tagSelectionModel = new FilterableDropdownSelectionModel()
correspondentSelectionModel = new FilterableDropdownSelectionModel()
@@ -75,12 +112,28 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
dateAddedBefore: string
dateAddedAfter: string
_unmodifiedFilterRules: FilterRule[] = []
_filterRules: FilterRule[] = []
@Input()
set unmodifiedFilterRules(value: FilterRule[]) {
this._unmodifiedFilterRules = value
this.checkIfRulesHaveChanged()
}
get unmodifiedFilterRules(): FilterRule[] {
return this._unmodifiedFilterRules
}
@Input()
set filterRules (value: FilterRule[]) {
this._filterRules = value
this.documentTypeSelectionModel.clear(false)
this.tagSelectionModel.clear(false)
this.correspondentSelectionModel.clear(false)
this._titleFilter = null
this._textFilter = null
this._moreLikeId = null
this.dateAddedBefore = null
this.dateAddedAfter = null
this.dateCreatedBefore = null
@@ -89,7 +142,28 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
value.forEach(rule => {
switch (rule.rule_type) {
case FILTER_TITLE:
this._titleFilter = rule.value
this._textFilter = rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE
break
case FILTER_TITLE_CONTENT:
this._textFilter = rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
break
case FILTER_ASN:
this._textFilter = rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_ASN
break
case FILTER_FULLTEXT_QUERY:
this._textFilter = rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_QUERY
break
case FILTER_FULLTEXT_MORELIKE:
this._moreLikeId = +rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
this.documentService.get(this._moreLikeId).subscribe(result => {
this._moreLikeDoc = result
this._textFilter = result.title
})
break
case FILTER_CREATED_AFTER:
this.dateCreatedAfter = rule.value
@@ -117,12 +191,25 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
break
}
})
this.checkIfRulesHaveChanged()
}
get filterRules(): FilterRule[] {
let filterRules: FilterRule[] = []
if (this._titleFilter) {
filterRules.push({rule_type: FILTER_TITLE, value: this._titleFilter})
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT) {
filterRules.push({rule_type: FILTER_TITLE_CONTENT, value: this._textFilter})
}
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE) {
filterRules.push({rule_type: FILTER_TITLE, value: this._textFilter})
}
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_ASN) {
filterRules.push({rule_type: FILTER_ASN, value: this._textFilter})
}
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY) {
filterRules.push({rule_type: FILTER_FULLTEXT_QUERY, value: this._textFilter})
}
if (this._moreLikeId && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
filterRules.push({rule_type: FILTER_FULLTEXT_MORELIKE, value: this._moreLikeId?.toString()})
}
if (this.tagSelectionModel.isNoneSelected()) {
filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"})
@@ -155,25 +242,40 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
@Output()
filterRulesChange = new EventEmitter<FilterRule[]>()
@Output()
reset = new EventEmitter()
@Input()
rulesModified: boolean = false
private checkIfRulesHaveChanged() {
let modified = false
if (this._unmodifiedFilterRules.length != this._filterRules.length) {
modified = true
} else {
modified = this._unmodifiedFilterRules.some(rule => {
return (this._filterRules.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined)
})
if (!modified) {
// only check other direction if we havent already determined is modified
modified = this._filterRules.some(rule => {
this._unmodifiedFilterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined
})
}
}
this.rulesModified = modified
}
updateRules() {
this.filterRulesChange.next(this.filterRules)
}
get titleFilter() {
return this._titleFilter
get textFilter() {
return this._textFilter
}
set titleFilter(value) {
this.titleFilterDebounce.next(value)
set textFilter(value) {
this.textFilterDebounce.next(value)
}
titleFilterDebounce: Subject<string>
textFilterDebounce: Subject<string>
subscription: Subscription
ngOnInit() {
@@ -181,23 +283,29 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
this.correspondentService.listAll().subscribe(result => this.correspondents = result.results)
this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results)
this.titleFilterDebounce = new Subject<string>()
this.textFilterDebounce = new Subject<string>()
this.subscription = this.titleFilterDebounce.pipe(
this.subscription = this.textFilterDebounce.pipe(
debounceTime(400),
distinctUntilChanged()
).subscribe(title => {
this._titleFilter = title
).subscribe(text => {
this._textFilter = text
this.documentService.searchQuery = text
this.updateRules()
})
if (this._textFilter) this.documentService.searchQuery = this._textFilter
}
ngOnDestroy() {
this.titleFilterDebounce.complete()
this.textFilterDebounce.complete()
}
resetSelected() {
this.reset.next()
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
this.filterRules = this._unmodifiedFilterRules
this.updateRules()
}
toggleTag(tagId: number) {
@@ -223,4 +331,13 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
onDocumentTypeDropdownOpen() {
this.documentTypeSelectionModel.apply()
}
changeTextFilterTarget(target) {
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE && target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
this._textFilter = ""
}
this.textFilterTarget = target
this.textFilterInput.nativeElement.focus()
this.updateRules()
}
}

View File

@@ -0,0 +1,22 @@
::ng-deep .popover {
max-width: 40rem;
.preview {
min-width: 30rem;
min-height: 18rem;
max-height: 35rem;
overflow-y: scroll;
}
.spinner-border {
position: absolute;
top: 4rem;
left: calc(50% - 0.5rem);
z-index: 0;
}
}
::ng-deep .popover-hidden .popover {
opacity: 0;
pointer-events: none;
}

View File

@@ -8,7 +8,7 @@
<div class="modal-body">
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check>
</div>
<div class="modal-footer">

View File

@@ -9,7 +9,7 @@
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
</div>

View File

@@ -11,8 +11,8 @@
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="bg-dark p-3 mb-3 text-light text-monospace log-container">
<div class="bg-dark p-3 text-light text-monospace log-container" #logContainer>
<p
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
*ngFor="let log of logs" style="white-space: pre;">{{log}}</p>
*ngFor="let log of logs">{{log}}</p>
</div>

View File

@@ -16,9 +16,11 @@
}
.log-container {
overflow: scroll;
height: calc(100vh - 190px);
overflow-y: scroll;
height: calc(100vh - 200px);
top: 70px;
}
p {
white-space: pre-wrap;
}
}

View File

@@ -1,4 +1,4 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Component, ElementRef, OnInit, AfterViewChecked, ViewChild } from '@angular/core';
import { LogService } from 'src/app/services/rest/log.service';
@Component({
@@ -6,7 +6,7 @@ import { LogService } from 'src/app/services/rest/log.service';
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss']
})
export class LogsComponent implements OnInit {
export class LogsComponent implements OnInit, AfterViewChecked {
constructor(private logService: LogService) { }
@@ -16,6 +16,8 @@ export class LogsComponent implements OnInit {
activeLog: string
@ViewChild('logContainer') logContainer: ElementRef
ngOnInit(): void {
this.logService.list().subscribe(result => {
this.logFiles = result
@@ -26,6 +28,10 @@ export class LogsComponent implements OnInit {
})
}
ngAfterViewChecked() {
this.scrollToBottom();
}
reloadLogs() {
this.logService.get(this.activeLog).subscribe(result => {
this.logs = result
@@ -48,4 +54,12 @@ export class LogsComponent implements OnInit {
}
}
scrollToBottom(): void {
this.logContainer?.nativeElement.scroll({
top: this.logContainer.nativeElement.scrollHeight,
left: 0,
behavior: 'auto'
});
}
}

View File

@@ -96,6 +96,7 @@
<div class="col">
<app-input-check i18n-title title="Use system settings" formControlName="darkModeUseSystem"></app-input-check>
<app-input-check [hidden]="settingsForm.value.darkModeUseSystem" i18n-title title="Enable dark mode" formControlName="darkModeEnabled"></app-input-check>
<app-input-check i18n-title title="Invert thumbnails in dark mode" formControlName="darkModeInvertThumbs"></app-input-check>
</div>
</div>
@@ -110,11 +111,11 @@
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink i18n>Notifications</a>
<ng-template ngbNavContent>
<h4 i18n>Document processing</h4>
<div class="form-row form-group">

View File

@@ -21,6 +21,7 @@ export class SettingsComponent implements OnInit {
'documentListItemPerPage': new FormControl(this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)),
'darkModeUseSystem': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM)),
'darkModeEnabled': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED)),
'darkModeInvertThumbs': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)),
'useNativePdfViewer': new FormControl(this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)),
'savedViews': this.savedViewGroup,
'displayLanguage': new FormControl(this.settings.getLanguage()),
@@ -74,6 +75,7 @@ export class SettingsComponent implements OnInit {
this.settings.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage)
this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem)
this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString())
this.settings.set(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, (this.settingsForm.value.darkModeInvertThumbs == true).toString())
this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer)
this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale)
this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat)
@@ -88,14 +90,15 @@ export class SettingsComponent implements OnInit {
}
get displayLanguageOptions(): LanguageOption[] {
return [{code: "", name: $localize`Use system language`}].concat(this.settings.getLanguageOptions())
return [
{code: "", name: $localize`Use system language`}
].concat(this.settings.getLanguageOptions())
}
get dateLocaleOptions(): LanguageOption[] {
return [
{code: "", name: $localize`Use date format of display language`},
{code: "iso-8601", name: $localize`ISO 8601`}
].concat(this.settings.getLanguageOptions())
{code: "", name: $localize`Use date format of display language`}
].concat(this.settings.getDateLocaleOptions())
}
get today() {

View File

@@ -8,19 +8,11 @@
<div class="modal-body">
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
<div class="form-group paperless-input-select">
<label for="colour" i18n>Color</label>
<ng-select name="colour" formControlName="colour" [items]="getColours()" bindValue="id" bindLabel="name" [clearable]="false">
<ng-template ng-option-tmp ng-label-tmp let-item="item">
<span class="badge" [style.background]="item.value" [style.color]="item.textColor">{{item.name}}</span>
</ng-template>
</ng-select>
</div>
<app-input-color i18n-title title="Color" formControlName="color" [error]="error?.color"></app-input-color>
<app-input-check i18n-title title="Inbox tag" formControlName="is_inbox_tag" i18n-hint hint="Inbox tags are automatically assigned to all consumed documents."></app-input-check>
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
</div>
<div class="modal-footer">

View File

@@ -2,9 +2,10 @@ 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 { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
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';
@Component({
selector: 'app-tag-edit-dialog',
@@ -13,7 +14,7 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
constructor(service: TagService, activeModal: NgbActiveModal, toastService: ToastService) {
constructor(service: TagService, activeModal: NgbActiveModal, toastService: ToastService) {
super(service, activeModal, toastService)
}
@@ -28,7 +29,7 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
getForm(): FormGroup {
return new FormGroup({
name: new FormControl(''),
colour: new FormControl(1),
color: new FormControl(randomColor()),
is_inbox_tag: new FormControl(false),
matching_algorithm: new FormControl(1),
match: new FormControl(""),
@@ -36,12 +37,4 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
})
}
getColours() {
return TAG_COLOURS
}
getColor(id: number) {
return TAG_COLOURS.find(c => c.id == id)
}
}

View File

@@ -26,8 +26,8 @@
<tbody>
<tr *ngFor="let tag of data">
<td scope="row">{{ tag.name }}</td>
<td scope="row"><span class="badge" [style.color]="getColor(tag.colour).textColor"
[style.background-color]="getColor(tag.colour).value">{{ getColor(tag.colour).name }}</span></td>
<td scope="row"><span class="badge" [style.color]="tag.text_color"
[style.background-color]="tag.color">{{tag.color}}</span></td>
<td scope="row">{{ getMatching(tag) }}</td>
<td scope="row">{{ tag.document_count }}</td>
<td scope="row">

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { FILTER_HAS_TAG } from 'src/app/data/filter-rule-type';
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
import { PaperlessTag } from 'src/app/data/paperless-tag';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { TagService } from 'src/app/services/rest/tag.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -22,10 +22,6 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> {
super(tagService, modalService, TagEditDialogComponent, toastService)
}
getColor(id) {
return TAG_COLOURS.find(c => c.id == id)
}
getDeleteMessage(object: PaperlessTag) {
return $localize`Do you really want to delete the tag "${object.name}"?`
}

View File

@@ -1,3 +0,0 @@
... <span *ngFor="let fragment of highlights">
<span *ngFor="let token of fragment" [class.match]="token.highlight">{{token.text}}</span> ...
</span>

View File

@@ -1,4 +0,0 @@
.match {
color: black;
background-color: rgb(255, 211, 66);
}

View File

@@ -1,19 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { SearchHitHighlight } from 'src/app/data/search-result';
@Component({
selector: 'app-result-highlight',
templateUrl: './result-highlight.component.html',
styleUrls: ['./result-highlight.component.scss']
})
export class ResultHighlightComponent implements OnInit {
constructor() { }
@Input()
highlights: SearchHitHighlight[][]
ngOnInit(): void {
}
}

View File

@@ -1,28 +0,0 @@
<app-page-header i18n-title title="Search results">
</app-page-header>
<div *ngIf="errorMessage" class="alert alert-danger" i18n>Invalid search query: {{errorMessage}}</div>
<p *ngIf="more_like" i18n>
Showing documents similar to <a routerLink="/documents/{{more_like}}">{{more_like_doc?.original_file_name}}</a>
</p>
<p *ngIf="query">
<ng-container i18n>Search query: <i>{{query}}</i></ng-container>
<ng-container *ngIf="correctedQuery">
- <ng-container i18n>Did you mean "<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}</a>"?</ng-container>
</ng-container>
</p>
<div *ngIf="!errorMessage" [class.result-content-searching]="searching" infiniteScroll (scrolled)="onScroll()">
<p i18n>{resultCount, plural, =0 {No results} =1 {One result} other {{{resultCount}} results}}</p>
<ng-container *ngFor="let result of results">
<app-document-card-large *ngIf="result.document"
[document]="result.document"
[details]="result.highlights"
[searchScore]="result.score / maxScore"
[moreLikeThis]="true">
</app-document-card-large>
</ng-container>
</div>

View File

@@ -1,15 +0,0 @@
.result-content {
color: darkgray;
}
.doc-img {
object-fit: cover;
object-position: top;
height: 100%;
position: absolute;
}
.result-content-searching {
opacity: 0.3;
}

View File

@@ -1,95 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
import { SearchHit } from 'src/app/data/search-result';
import { DocumentService } from 'src/app/services/rest/document.service';
import { SearchService } from 'src/app/services/rest/search.service';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
results: SearchHit[] = []
query: string = ""
more_like: number
more_like_doc: PaperlessDocument
searching = false
currentPage = 1
pageCount = 1
resultCount
correctedQuery: string = null
errorMessage: string
get maxScore() {
return this.results?.length > 0 ? this.results[0].score : 100
}
constructor(private searchService: SearchService, private route: ActivatedRoute, private router: Router, private documentService: DocumentService) { }
ngOnInit(): void {
this.route.queryParamMap.subscribe(paramMap => {
window.scrollTo(0, 0)
this.query = paramMap.get('query')
this.more_like = paramMap.has('more_like') ? +paramMap.get('more_like') : null
if (this.more_like) {
this.documentService.get(this.more_like).subscribe(r => {
this.more_like_doc = r
})
} else {
this.more_like_doc = null
}
this.searching = true
this.currentPage = 1
this.loadPage()
})
}
searchCorrectedQuery() {
this.router.navigate(["search"], {queryParams: {query: this.correctedQuery, more_like: this.more_like}})
}
loadPage(append: boolean = false) {
this.errorMessage = null
this.correctedQuery = null
this.searchService.search(this.query, this.currentPage, this.more_like).subscribe(result => {
if (append) {
this.results.push(...result.results)
} else {
this.results = result.results
}
this.pageCount = result.page_count
this.searching = false
this.resultCount = result.count
this.correctedQuery = result.corrected_query
}, error => {
this.searching = false
this.resultCount = 1
this.pageCount = 1
this.results = []
this.errorMessage = error.error
})
}
onScroll() {
if (this.currentPage < this.pageCount) {
this.currentPage += 1
this.loadPage(true)
}
}
}

View File

@@ -20,6 +20,11 @@ export const FILTER_DOES_NOT_HAVE_TAG = 17
export const FILTER_ASN_ISNULL = 18
export const FILTER_TITLE_CONTENT = 19
export const FILTER_FULLTEXT_QUERY = 20
export const FILTER_FULLTEXT_MORELIKE = 21
export const FILTER_RULE_TYPES: FilterRuleType[] = [
{id: FILTER_TITLE, filtervar: "title__icontains", datatype: "string", multi: false, default: ""},
@@ -47,7 +52,13 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
{id: FILTER_MODIFIED_BEFORE, filtervar: "modified__date__lt", datatype: "date", multi: false},
{id: FILTER_MODIFIED_AFTER, filtervar: "modified__date__gt", datatype: "date", multi: false},
{id: FILTER_ASN_ISNULL, filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false}
{id: FILTER_ASN_ISNULL, filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false},
{id: FILTER_TITLE_CONTENT, filtervar: "title_content", datatype: "string", multi: false},
{id: FILTER_FULLTEXT_QUERY, filtervar: "query", datatype: "string", multi: false},
{id: FILTER_FULLTEXT_MORELIKE, filtervar: "more_like_id", datatype: "number", multi: false},
]
export interface FilterRuleType {

View File

@@ -4,6 +4,15 @@ import { PaperlessTag } from './paperless-tag'
import { PaperlessDocumentType } from './paperless-document-type'
import { Observable } from 'rxjs'
export interface SearchHit {
score?: number
rank?: number
highlights?: string
}
export interface PaperlessDocument extends ObjectWithId {
correspondent$?: Observable<PaperlessCorrespondent>
@@ -40,4 +49,6 @@ export interface PaperlessDocument extends ObjectWithId {
archive_serial_number?: number
__search_hit__?: SearchHit
}

View File

@@ -1,26 +1,10 @@
import { MatchingModel } from './matching-model';
import { ObjectWithId } from './object-with-id';
export const TAG_COLOURS = [
{id: 1, value: "#a6cee3", name: $localize`Light blue`, textColor: "#000000"},
{id: 2, value: "#1f78b4", name: $localize`Blue`, textColor: "#ffffff"},
{id: 3, value: "#b2df8a", name: $localize`Light green`, textColor: "#000000"},
{id: 4, value: "#33a02c", name: $localize`Green`, textColor: "#ffffff"},
{id: 5, value: "#fb9a99", name: $localize`Light red`, textColor: "#000000"},
{id: 6, value: "#e31a1c", name: $localize`Red `, textColor: "#ffffff"},
{id: 7, value: "#fdbf6f", name: $localize`Light orange`, textColor: "#000000"},
{id: 8, value: "#ff7f00", name: $localize`Orange`, textColor: "#000000"},
{id: 9, value: "#cab2d6", name: $localize`Light violet`, textColor: "#000000"},
{id: 10, value: "#6a3d9a", name: $localize`Violet`, textColor: "#ffffff"},
{id: 11, value: "#b15928", name: $localize`Brown`, textColor: "#ffffff"},
{id: 12, value: "#000000", name: $localize`Black`, textColor: "#ffffff"},
{id: 13, value: "#cccccc", name: $localize`Light grey`, textColor: "#000000"}
]
import { MatchingModel } from "./matching-model";
export interface PaperlessTag extends MatchingModel {
colour?: number
color?: string
text_color?: string
is_inbox_tag?: boolean

View File

@@ -1,29 +0,0 @@
import { PaperlessDocument } from './paperless-document'
export class SearchHitHighlight {
text?: string
term?: number
}
export interface SearchHit {
id?: number
title?: string
score?: number
rank?: number
highlights?: SearchHitHighlight[][]
document?: PaperlessDocument
}
export interface SearchResult {
count?: number
page?: number
page_count?: number
corrected_query?: string
results?: SearchHit[]
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ApiVersionInterceptor } from './api-version.interceptor';
describe('ApiVersionInterceptor', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [
ApiVersionInterceptor
]
}));
it('should be created', () => {
const interceptor: ApiVersionInterceptor = TestBed.inject(ApiVersionInterceptor);
expect(interceptor).toBeTruthy();
});
});

View File

@@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
@Injectable()
export class ApiVersionInterceptor implements HttpInterceptor {
constructor() {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
request = request.clone({
setHeaders: {
'Accept': `application/json; version=${environment.apiVersion}`
}
})
return next.handle(request);
}
}

View File

@@ -4,8 +4,8 @@ import { SettingsService, SETTINGS_KEYS } from '../services/settings.service';
const FORMAT_TO_ISO_FORMAT = {
"longDate": "y-MM-dd",
"mediumDate": "yy-MM-dd",
"shortDate": "yy-MM-dd"
"mediumDate": "y-MM-dd",
"shortDate": "y-MM-dd"
}
@Pipe({
@@ -13,17 +13,20 @@ const FORMAT_TO_ISO_FORMAT = {
})
export class CustomDatePipe extends DatePipe implements PipeTransform {
private defaultLocale: string
constructor(@Inject(LOCALE_ID) locale: string, private settings: SettingsService) {
super(locale)
this.defaultLocale = locale
}
transform(value: any, format?: string, timezone?: string, locale?: string): string | null {
let l = locale || this.settings.get(SETTINGS_KEYS.DATE_LOCALE)
let l = locale || this.settings.get(SETTINGS_KEYS.DATE_LOCALE) || this.defaultLocale
let f = format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT)
if (l == "iso-8601") {
return super.transform(value, FORMAT_TO_ISO_FORMAT[f], timezone)
} else {
return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, locale)
return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, l)
}
}

View File

@@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { cloneFilterRules, FilterRule } from '../data/filter-rule';
import { FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY } from '../data/filter-rule-type';
import { PaperlessDocument } from '../data/paperless-document';
import { PaperlessSavedView } from '../data/paperless-saved-view';
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys';
@@ -38,6 +39,7 @@ interface ListViewState {
export class DocumentListViewService {
isReloading: boolean = false
error: string = null
rangeSelectionAnchorIndex: number
lastRangeSelectionToIndex: number
@@ -101,6 +103,7 @@ export class DocumentListViewService {
reload(onFinish?) {
this.isReloading = true
this.error = null
let activeListViewState = this.activeListViewState
this.documentService.listFiltered(
@@ -124,12 +127,17 @@ export class DocumentListViewService {
// this happens when applying a filter: the current page might not be available anymore due to the reduced result set.
activeListViewState.currentPage = 1
this.reload()
} else {
this.error = error.error
}
})
}
set filterRules(filterRules: FilterRule[]) {
this.activeListViewState.filterRules = filterRules
if (filterRules.find(r => (r.rule_type == FILTER_FULLTEXT_QUERY || r.rule_type == FILTER_FULLTEXT_MORELIKE))) {
this.activeListViewState.currentPage = 1
}
this.reload()
this.reduceSelectionToFilter()
this.saveDocumentListView()
@@ -197,7 +205,7 @@ export class DocumentListViewService {
sortField: this.activeListViewState.sortField,
sortReverse: this.activeListViewState.sortReverse
}
sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(savedState))
localStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(savedState))
}
}
@@ -207,7 +215,11 @@ export class DocumentListViewService {
this.activeListViewState.currentPage = 1
this.reduceSelectionToFilter()
this.saveDocumentListView()
this.router.navigate(["documents"])
if (this.router.url == "/documents") {
this.reload()
} else {
this.router.navigate(["documents"])
}
}
getLastPage(): number {
@@ -317,8 +329,8 @@ export class DocumentListViewService {
return this.documents.map(d => d.id).indexOf(documentID)
}
constructor(private documentService: DocumentService, private settings: SettingsService, private router: Router) {
let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
constructor(private documentService: DocumentService, private settings: SettingsService, private router: Router, private route: ActivatedRoute) {
let documentListViewConfigJson = localStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
if (documentListViewConfigJson) {
try {
let savedState: ListViewState = JSON.parse(documentListViewConfigJson)
@@ -332,7 +344,7 @@ export class DocumentListViewService {
let newState = Object.assign(this.defaultListViewState(), savedState)
this.listViewStates.set(null, newState)
} catch (e) {
sessionStorage.removeItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
localStorage.removeItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
}
}
}

View File

@@ -39,6 +39,8 @@ export interface SelectionData {
})
export class DocumentService extends AbstractPaperlessService<PaperlessDocument> {
private _searchQuery: string
constructor(http: HttpClient, private correspondentService: CorrespondentService, private documentTypeService: DocumentTypeService, private tagService: TagService) {
super(http, 'documents')
}
@@ -92,6 +94,7 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
getPreviewUrl(id: number, original: boolean = false): string {
let url = this.getResourceUrl(id, 'preview')
if (this._searchQuery) url += `#search="${this._searchQuery}"`
if (original) {
url += "?original=true"
}
@@ -138,4 +141,8 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
return this.http.post(this.getResourceUrl(null, 'bulk_download'), {"documents": ids, "content": content}, { responseType: 'blob' })
}
public set searchQuery(query: string) {
this._searchQuery = query
}
}

View File

@@ -2,8 +2,6 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { SearchResult } from 'src/app/data/search-result';
import { environment } from 'src/environments/environment';
import { DocumentService } from './document.service';
@@ -13,30 +11,7 @@ import { DocumentService } from './document.service';
})
export class SearchService {
constructor(private http: HttpClient, private documentService: DocumentService) { }
search(query: string, page?: number, more_like?: number): Observable<SearchResult> {
let httpParams = new HttpParams()
if (query) {
httpParams = httpParams.set('query', query)
}
if (page) {
httpParams = httpParams.set('page', page.toString())
}
if (more_like) {
httpParams = httpParams.set('more_like', more_like.toString())
}
return this.http.get<SearchResult>(`${environment.apiBaseUrl}search/`, {params: httpParams}).pipe(
map(result => {
result.results.forEach(hit => {
if (hit.document) {
this.documentService.addObservablesToDocument(hit.document)
}
})
return result
})
)
}
constructor(private http: HttpClient) { }
autocomplete(term: string): Observable<string[]> {
return this.http.get<string[]>(`${environment.apiBaseUrl}search/autocomplete/`, {params: new HttpParams().set('term', term)})

View File

@@ -1,5 +1,5 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
import { Inject, Injectable, LOCALE_ID, Renderer2, RendererFactory2 } from '@angular/core';
import { Meta } from '@angular/platform-browser';
import { CookieService } from 'ngx-cookie-service';
@@ -10,9 +10,14 @@ export interface PaperlessSettings {
}
export interface LanguageOption {
code: string,
name: string,
code: string
name: string
englishName?: string
/**
* A date format string for use by the date selectors. MUST contain 'yyyy', 'mm' and 'dd'.
*/
dateInputFormat?: string
}
export const SETTINGS_KEYS = {
@@ -21,6 +26,7 @@ export const SETTINGS_KEYS = {
DOCUMENT_LIST_SIZE: 'general-settings:documentListSize',
DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system',
DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled',
DARK_MODE_THUMB_INVERTED: 'general-settings:dark-mode:thumb-inverted',
USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer',
DATE_LOCALE: 'general-settings:date-display:date-locale',
DATE_FORMAT: 'general-settings:date-display:date-format',
@@ -36,6 +42,7 @@ const SETTINGS: PaperlessSettings[] = [
{key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, type: "number", default: 50},
{key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, type: "boolean", default: true},
{key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: "boolean", default: false},
{key: SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, type: "boolean", default: true},
{key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, type: "boolean", default: false},
{key: SETTINGS_KEYS.DATE_LOCALE, type: "string", default: ""},
{key: SETTINGS_KEYS.DATE_FORMAT, type: "string", default: "mediumDate"},
@@ -56,7 +63,8 @@ export class SettingsService {
private rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document,
private cookieService: CookieService,
private meta: Meta
private meta: Meta,
@Inject(LOCALE_ID) private localeId: string
) {
this.renderer = rendererFactory.createRenderer(null, null);
@@ -79,14 +87,26 @@ export class SettingsService {
getLanguageOptions(): LanguageOption[] {
return [
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)"},
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)"},
{code: "de", name: $localize`German`, englishName: "German"},
{code: "nl", name: $localize`Dutch`, englishName: "Dutch"},
{code: "fr", name: $localize`French`, englishName: "French"}
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
{code: "de-de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
{code: "nl-nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
{code: "fr-fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
{code: "pt-pt", name: $localize`Portuguese`, englishName: "Portuguese", dateInputFormat: "dd/mm/yyyy"},
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"},
{code: "it-it", name: $localize`Italian`, englishName: "Italian", dateInputFormat: "dd/mm/yyyy"},
{code: "ro-ro", name: $localize`Romanian`, englishName: "Romanian", dateInputFormat: "dd.mm.yyyy"},
{code: "ru-ru", name: $localize`Russian`, englishName: "Russian", dateInputFormat: "dd.mm.yyyy"},
{code: "es-es", name: $localize`Spanish`, englishName: "Spanish", dateInputFormat: "dd/mm/yyyy"},
]
}
getDateLocaleOptions(): LanguageOption[] {
let isoOption: LanguageOption = {code: "iso-8601", name: $localize`ISO 8601`, dateInputFormat: "yyyy-mm-dd"}
return [isoOption].concat(this.getLanguageOptions())
}
private getLanguageCookieName() {
let prefix = ""
if (this.meta.getTag('name=cookie_prefix')) {
@@ -107,6 +127,11 @@ export class SettingsService {
}
}
getLocalizedDateInputFormat(): string {
let dateLocale = this.get(SETTINGS_KEYS.DATE_LOCALE) || this.getLanguage() || this.localeId.toLowerCase()
return this.getDateLocaleOptions().find(o => o.code == dateLocale)?.dateInputFormat || "yyyy-mm-dd"
}
get(key: string): any {
let setting = SETTINGS.find(s => s.key == key)

View File

@@ -0,0 +1,48 @@
function componentToHex(c) {
var hex = Math.floor(c).toString(16)
return hex.length == 1 ? "0" + hex : hex
}
/**
* https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
*
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param Number h The hue
* @param Number s The saturation
* @param Number l The lightness
* @return Array The RGB representation
*/
function hslToRgb(h, s, l){
var r, g, b
if(s == 0){
r = g = b = l // achromatic
}else{
function hue2rgb(p, q, t){
if(t < 0) t += 1
if(t > 1) t -= 1
if(t < 1/6) return p + (q - p) * 6 * t
if(t < 1/2) return q
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6
return p
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s
var p = 2 * l - q
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
}
return [r * 255, g * 255, b * 255]
}
export function randomColor() {
let rgb = hslToRgb(Math.random(), 0.6, Math.random() * 0.4 + 0.4)
return `#${componentToHex(rgb[0])}${componentToHex(rgb[1])}${componentToHex(rgb[2])}`
}

View File

@@ -0,0 +1,59 @@
import { Injectable } from "@angular/core"
import { NgbDateParserFormatter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap"
import { SettingsService } from "../services/settings.service"
@Injectable()
export class LocalizedDateParserFormatter extends NgbDateParserFormatter {
constructor(private settings: SettingsService) {
super()
}
private getDateInputFormat() {
return this.settings.getLocalizedDateInputFormat()
}
/**
* This constructs a regular expression from a date input format which is then
* used to parse dates.
*/
private getDateParseRegex() {
return new RegExp(
"^" + this.getDateInputFormat()
.replace('dd', '(?<day>[0-9]+)')
.replace('mm', '(?<month>[0-9]+)')
.replace('yyyy', '(?<year>[0-9]+)')
.split('.').join('\\.\\s*') + "$" // allow whitespace(s) after dot (specific for German)
)
}
parse(value: string): NgbDateStruct | null {
let match = this.getDateParseRegex().exec(value)
if (match) {
let dateStruct = {
day: +match.groups.day,
month: +match.groups.month,
year: +match.groups.year
}
if (dateStruct.year <= (new Date().getFullYear() - 2000)) {
dateStruct.year += 2000
} else if (dateStruct.year < 100) {
dateStruct.year += 1900
}
return dateStruct
} else {
return null
}
}
format(date: NgbDateStruct | null): string {
if (date) {
return this.getDateInputFormat()
.replace('dd', date.day.toString().padStart(2, '0'))
.replace('mm', date.month.toString().padStart(2, '0'))
.replace('yyyy', date.year.toString().padStart(4, '0'))
} else {
return null
}
}
}

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