Compare commits

...
Author SHA1 Message Date
jonaswinkler 27ba139810 workaround for issue with pikepdf 2021-05-13 14:15:42 +02:00
jonaswinkler 27ff38b79e Merge branch 'dev' into feature-merge-tool 2021-05-13 14:08:31 +02:00
jonaswinkler b89b51b121 Merge branch 'master' into dev 2021-05-09 16:22:21 +02:00
Jonas Winkler 1b31388232 Merge pull request #995 from cmer/prevent-robot-indexing
Tell web crawlers to not index Paperless
2021-05-07 22:30:49 +02:00
Jonas Winkler 8256e73f04 Merge pull request #994 from shamoon/fix/issue-985
Respect user dark mode setting on login page
2021-05-07 22:30:03 +02:00
Carl Mercier fc4e59ec00 Tell web crawlers to not index Paperless 2021-05-07 14:02:11 -04:00
Michael Shamoon 428e00ba23 Respect user dark mode setting on login page 2021-05-07 10:50:08 -07:00
Jonas Winkler 2ccbb08c3f Merge pull request #992 from muued/patch-2
Mention lacking support for ARM in ansible setup
2021-05-06 20:58:59 +02:00
Jonas Winkler eefc026a17 Merge pull request #991 from muued/patch-1
Use ansible-galaxy for ansible setup
2021-05-06 17:51:21 +02:00
Fabian Ohler d89022a280 Mention lacking support for ARM in ansible setup
Since the jbig2enc dependency is pulled from a repository that only provides i386 and amd64 packages, the installation will fail on arm hosts.
2021-05-06 14:15:12 +02:00
Fabian Ohler 93bd24c9d2 Update setup.rst
use ansible-galaxy to fetch the ansible scripts to have a role called paperless-ng instead of ansible
2021-05-06 14:08:49 +02:00
Jonas Winkler 197aec2945 New Crowdin updates (#988)
* New translations django.po (French)
[ci skip]

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

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

* New translations messages.xlf (Portuguese)
[ci skip]
2021-05-05 22:36:22 +02:00
Jonas Winkler e16d58cff5 New Crowdin updates (#986) 2021-05-05 14:49:08 +02:00
Jonas Winkler 92667009be add swedish locale 2021-05-04 17:01:39 +02:00
Jonas Winkler c93657f40e New Crowdin updates (#976) 2021-05-04 15:28:10 +02:00
Jonas Winkler 16658f5939 Merge pull request #965 from jovandeginste/add-rtf
Add support for rtf
2021-04-30 19:11:07 +02:00
Jo Vandeginste ec0af59596 Add support for rtf
Signed-off-by: Jo Vandeginste <Jo.Vandeginste@kuleuven.be>
2021-04-30 13:19:12 +02:00
Jonas Winkler 9e1f9d6d9d Merge pull request #960 from shamoon/fix/issue-65-2
Remove PDF type attribute to fix macOS Safari native PDF viewer crashing
2021-04-28 16:45:22 +02:00
Michael Shamoon e652b927ce Remove type attribute of pdf object in document detail 2021-04-28 07:32:18 -07:00
Jonas Winkler 5a97f3f94b update libseccomp2 2021-04-28 02:12:55 +02:00
Jonas Winkler 9494c243a4 changelog, version bump 2021-04-27 22:52:50 +02:00
Jonas Winkler c0a1be4325 Merge pull request #956 from WhiteHatTux/bugfix/879
Use gosu as it is less complicated than sudo
2021-04-27 09:59:05 +02:00
Jonas Winkler 70fda8e2f8 New Crowdin updates (#932) 2021-04-27 09:52:30 +02:00
Christopher Timm 5765893f69 Use gosu instead of sudo for easier configuration
#879
2021-04-26 19:06:30 -05:00
jonaswinkler 17b9d7eb42 Merge branch 'master' into dev 2021-04-18 12:41:52 +02:00
jonaswinkler 0a897cae32 bugfix 2021-04-18 11:29:12 +02:00
jonaswinkler 37f56e824a Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-04-18 11:25:21 +02:00
jonaswinkler a8e7ec0e96 changelog, version bump 2021-04-18 11:25:11 +02:00
Jonas Winkler b86d853cf1 New Crowdin updates (#931) 2021-04-18 10:49:22 +02:00
Jonas Winkler da0c94c9a1 New Crowdin updates (#930) 2021-04-17 22:33:42 +02:00
jonaswinkler 0547fd2760 fix minor issue 2021-04-17 22:33:06 +02:00
jonaswinkler e0a6df8435 Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-04-17 22:30:45 +02:00
jonaswinkler c2d4ca7566 lets hope this works 2021-04-17 22:29:54 +02:00
jonaswinkler 56ce278bbd update FAQ 2021-04-17 22:29:24 +02:00
Jonas Winkler e112541f5b New Crowdin updates (#929) 2021-04-17 20:34:57 +02:00
jonaswinkler 17c7b0840b changes for django 3.2 2021-04-17 16:00:29 +02:00
jonaswinkler 68658bd407 update dependencies 2021-04-17 14:56:25 +02:00
Jonas Winkler 9184845a9d Merge pull request #890 from what-name/feature/superuser-manager
Create initial Paperless user automatically
2021-04-17 14:44:01 +02:00
Jonas Winkler 8ddb2fb305 New Crowdin updates (#928) 2021-04-17 14:37:31 +02:00
jonaswinkler 96f53986b7 pycodestyle 2021-04-17 14:36:04 +02:00
jonaswinkler 8566682209 add test case, update password if changed 2021-04-17 14:33:07 +02:00
jonaswinkler 4f9ae17059 Merge branch 'dev' into feature/superuser-manager
# Conflicts:
#	docker/docker-entrypoint.sh
2021-04-17 14:10:56 +02:00
jonaswinkler 7f9f805c5f Merge branch 'dev' into feature/superuser-manager 2021-04-17 14:01:42 +02:00
Jonas Winkler 5208e81715 Merge pull request #856 from sbrl/patch-1
docker-entrypoint.sh: don't crash on chown errors
2021-04-17 13:57:14 +02:00
Jonas Winkler 01a786c117 Merge pull request #894 from lknop/master
add single compose file for usage in portainer environments
2021-04-17 13:50:11 +02:00
Jonas Winkler 8bf4f31954 Merge pull request #918 from shamoon/fix/issue-907
"Sticky" filter editor and bulk editor
2021-04-17 13:48:54 +02:00
jonaswinkler 07ab3e98ed add pl-PL locale 2021-04-17 13:41:26 +02:00
Jonas Winkler ba5899de7b New Crowdin updates (#874) 2021-04-17 13:34:42 +02:00
Michael Shamoon a4fef056ed Fix breakpoint
File missing from last commit
2021-04-14 13:24:03 -07:00
Michael Shamoon c266cd6aae Spacing tweaks, mobile breakpoint 2021-04-14 09:34:39 -07:00
Michael Shamoon 43d05b2e6d Hide visible underlying shadows on filter / bulk editor 2021-04-14 09:00:57 -07:00
Michael Shamoon 6f0c14cc07 Make filter editor / bulk editor 'sticky' on scroll 2021-04-14 09:00:30 -07:00
Lukasz Knop 2eac8fa91c disabled unnecessary overrides, copied variable comments 2021-04-11 21:48:50 +02:00
Lukasz Knop 31f5e178b3 add single compose file for usage in portainer environments 2021-04-11 15:16:04 +02:00
Chris Nagy c3d7088168 Fix code guidelines 2021-04-10 16:10:45 +02:00
Chris Nagy a9e11b1cb7 Add newline end of file 2021-04-10 16:02:48 +02:00
Chris Nagy ee51a0be13 Minor docs change 2021-04-10 15:08:33 +02:00
jonaswinkler 1091387f48 better logging for the retagger 2021-04-10 14:38:39 +02:00
jonaswinkler 4db75537cb install sudo from stable 2021-04-10 14:38:19 +02:00
Chris Nagy cb4e738a0f Fix env variable in docker entrypoint 2021-04-10 13:50:52 +02:00
Chris Nagy 0cc32e7ed7 Extend docs with superuser 2021-04-10 13:47:31 +02:00
Jonas Winkler 94f90e1e81 Merge pull request #887 from alexpovel/master
Fix typo
2021-04-10 00:21:45 +02:00
Alex Povel ce8f315747 Fix typo
See also https://github.com/jonaswinkler/paperless-ng/blob/a3c4e4c6b65086f9c1af9569b9a771ef8320d917/docker/compose/docker-compose.env#L30
2021-04-09 22:14:09 +02:00
Starbeamrainbowlabs b761a549c0 docker-prepare.sh: remove rogue instances of sudo
This is being done by calling the script as the correct user in the 
first place.
2021-04-08 03:06:01 +01:00
Starbeamrainbowlabs dd7c5da256 docker-entrypoint.sh: split non-root tasks into docker-prepare.sh 2021-04-08 00:03:55 +01:00
Starbeamrainbowlabs 02dd7ec615 docker: mark scripts as executable 2021-04-07 23:19:20 +01:00
Jonas Winkler b7b448b870 Merge pull request #875 from shamoon/fix/large-card-info-layout
Fix alignment of card info on large cards
2021-04-06 23:41:03 +02:00
Michael Shamoon b7326afe5a Fix alignment of card info on large cards 2021-04-06 14:25:59 -07:00
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
Jonas Winkler d521d7733b Merge pull request #789 from shamoon/feature/merge-tool-ui
Feature: merge tool UI
2021-04-03 22:26:57 +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
Michael Shamoon 99bde7b613 Dont import entire dark mode theme just for 1 color 2021-04-02 14:18:48 -07:00
Michael Shamoon cc5b7eb4e3 Center preview title when empty 2021-04-02 11:10:42 -07:00
Michael Shamoon 2b4a0efef8 Move add to split merge directly into bulk editor 2021-04-02 10:57:06 -07:00
Michael Shamoon 9502917df6 Fix overhang of separator background 2021-04-02 08:05:02 -07:00
Michael Shamoon 6f3c72ef9a Fix options text color not visible in light mode 2021-04-02 07:54:03 -07:00
Michael Shamoon 3915149ea1 Remove separators from first / last position when dragged 2021-04-02 07:51:24 -07:00
Michael Shamoon f07fb6e1b3 Fix tabs in light mode 2021-04-02 07:47:06 -07:00
Michael Shamoon e328c439ff Change to 5/7 column width layout 2021-04-02 07:28:54 -07:00
Michael Shamoon caa9a340a4 Increase height of dropzone 2021-04-02 07:27:42 -07:00
Michael Shamoon b07537aa9a Merge branch 'feature-merge-tool' into feature/merge-tool-ui 2021-04-02 07:11:05 -07:00
jonaswinkler 8e35e6c726 fix test cases 2021-04-02 15:32:00 +02:00
jonaswinkler 05a43a05a4 Merge branch 'dev' into feature-merge-tool 2021-04-02 15:17:44 +02: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
Michael Shamoon 846a997323 Better button responsive layout 2021-03-31 10:35:39 -07:00
Michael Shamoon e0231a4638 Dont allow separator at start of document 2021-03-31 10:24:04 -07:00
Michael Shamoon b4db388d58 Dont initialize page number until page is rendered 2021-03-31 10:23:49 -07:00
Michael Shamoon e957d9aa53 Changes missed from last commit, enables page numbers in previews 2021-03-30 13:56:17 -07: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 72031824bf Preview page numbers 2021-03-25 15:01:05 -07:00
Michael Shamoon ae60fc8c2b Fix popover shadow 2021-03-24 23:23:01 -07:00
Michael Shamoon cc3c7ad481 Apply danger on delete toggle to entire label 2021-03-24 23:21:23 -07:00
Michael Shamoon 484edce5ab Split button should respect pre-selected pages 2021-03-24 23:12:02 -07:00
Michael Shamoon 66aee1446c Light mode fixes, styling tweaks 2021-03-24 22:36:19 -07:00
Michael Shamoon 971392345b Dont allow separator at the end of document parts list 2021-03-24 22:24:18 -07:00
Michael Shamoon c5ae3377bd Basic error handling 2021-03-24 22:16:51 -07:00
Michael Shamoon 4ddd031976 Slightly increasing button spacing on cards 2021-03-24 21:46:18 -07:00
Michael Shamoon c18180f344 Remove debug stuff 2021-03-24 21:46:06 -07:00
Michael Shamoon c07bbcb33f No return type on setter 2021-03-24 21:35:13 -07:00
Michael Shamoon 142c2ac9ff Sanitize pages input 2021-03-24 21:34:43 -07:00
Michael Shamoon 88bf679fd9 Unified button toolbar 2021-03-24 21:08:22 -07:00
Michael Shamoon a5de83fe7f Better clear button 2021-03-24 20:55:19 -07:00
Michael Shamoon 63b4c33fb9 Metadata options & source options merged into options popover panel 2021-03-24 20:37:50 -07:00
Michael Shamoon e0a1ba56dd Typescript fixes 2021-03-24 20:04:16 -07: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 aaa913f204 Options popover 2021-03-24 16:16:18 -07:00
Michael Shamoon cf5747f0bd Toggle for source document handling 2021-03-24 13:46:35 -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
Michael Shamoon 7df9e1f532 Translation stuff 2021-03-23 14:58:09 -07:00
Michael Shamoon 38d8b34ab4 Clear pages button 2021-03-23 14:57:59 -07:00
Michael Shamoon 24d07fcc17 Missing handle 2021-03-23 14:57:41 -07:00
Michael Shamoon 14116c85e7 Hide preview after all documents removed 2021-03-23 14:28:11 -07:00
Michael Shamoon 20b95fb506 Disable page chooser button if no page chosen 2021-03-23 14:24:42 -07:00
Michael Shamoon 3c25595fc2 Separator styling 2021-03-23 14:21:46 -07:00
Michael Shamoon af1034f84b Visual reorganization 2021-03-23 11:01:10 -07:00
Michael Shamoon cfc0925e04 Split support including tabbed previews 2021-03-23 00:03:21 -07:00
Michael Shamoon 746c03c198 Remove 'mode' concept, basic split UI, many visual changes 2021-03-22 22:31:52 -07: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
Michael Shamoon baae3c2701 Allow removing last document 2021-03-20 22:56:36 -07:00
Michael Shamoon d88cd05857 Allow empty pages field 2021-03-20 22:50:23 -07:00
Michael Shamoon 73c80c3f7a Page field formatting 2021-03-20 22:47:20 -07:00
Michael Shamoon 35f9989255 Allow input-debounce input 2021-03-20 22:04:49 -07:00
Michael Shamoon 33ddd033f2 Allow additional classes for input-debounce & fix field display 2021-03-20 21:45:31 -07:00
Michael Shamoon b8fabb623e InputDebounce component for page fields 2021-03-20 21:32:52 -07: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
Michael Shamoon 37c0fa4fab Merge branch 'feature-merge-tool' into feature/merge-tool-ui 2021-03-18 14:25:24 -07: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
Michael Shamoon 86ca37909b Basic page support 2021-03-16 22:36:59 -07:00
Michael Shamoon ce19525758 Change icons 2021-03-16 20:31:31 -07:00
Jonas Winkler 4cd1672094 New Crowdin updates (#787) 2021-03-17 02:14:43 +01:00
Michael Shamoon bb872ab56f Tooltip, random styling stuff 2021-03-16 15:31:54 -07:00
Michael Shamoon ea342831da Basic mode toggling 2021-03-16 14:53:23 -07: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
Michael Shamoon 8d0ac690fb Basic page chooser component 2021-03-16 10:29:22 -07: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
jonaswinkler e0f14941e8 added some test cases and validation 2021-03-16 10:20:58 +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
Michael Shamoon 7cbb50565c Basic duplicate & delete 2021-03-14 22:12:27 -07:00
Michael Shamoon 274df28f39 Spacer styling 2021-03-14 21:29:47 -07:00
Michael Shamoon 4a6160869c Naming 2021-03-14 21:27:58 -07:00
Michael Shamoon 87d969f44c Basic action buttons 2021-03-14 21:21:13 -07:00
Michael Shamoon 7746ab4522 Basic document chooser 2021-03-14 20:34:32 -07:00
Michael Shamoon 58cb42b44a Typescript 2021-03-14 14:53:32 -07:00
Michael Shamoon 8e330fc97a Basic live preview 2021-03-14 14:40:32 -07:00
Michael Shamoon 6a3ad77950 Skeleton UI for buttons 2021-03-14 14:02:01 -07:00
Chris Nagy e3ba968fef Add superuser management script 2021-03-14 19:43:22 +01:00
jonaswinkler 3d56621253 Merge branch 'dev' into feature-merge-tool 2021-03-14 19:22:08 +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
Michael Shamoon 9e6cb1b868 Basic layout elements, loading spinner 2021-03-14 09:30:29 -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
Michael Shamoon 321fad0564 Basic drag-n-drop 2021-03-13 22:34:47 -08:00
Michael Shamoon eb7b767e35 Basic button for initiating split/merge from bulk editor 2021-03-13 21:00:53 -08:00
Jonas Winkler 1bcfc5b54d New Crowdin updates (#752) 2021-03-13 23:19:03 +01:00
jonaswinkler 98b26b8e16 changed page specification 2021-03-13 19:29:11 +01:00
jonaswinkler 176d1fa2eb a couple changes 2021-03-13 18:21:36 +01:00
jonaswinkler 072134366c codestyle 2021-03-13 18:02:52 +01:00
jonaswinkler afd92af168 codestyle 2021-03-13 17:59:56 +01:00
jonaswinkler 2d61698509 couple changes 2021-03-13 17:56:38 +01:00
jonaswinkler 5e286798d5 better error messages 2021-03-13 17:48:51 +01:00
jonaswinkler b7a825301a support for overriding the date created 2021-03-13 17:48:38 +01:00
jonaswinkler 02da012603 added tests for merge 2021-03-13 17:47:54 +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
jonaswinkler 4d99e72a0e Merge branch 'dev' into feature-merge-tool 2021-03-12 23:24:16 +01: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 26c0568b1e some changes 2021-03-11 22:26:29 +01:00
jonaswinkler aa4d9782b7 initial gui #335 2021-03-11 22:23:01 +01:00
jonaswinkler 43ccf6682c move a couple things around 2021-03-11 22:09:23 +01:00
jonaswinkler 5e096bc8ce Angular interfaces for split+merge API support #335 2021-03-11 18:00:19 +01:00
jonaswinkler 1e131f0217 added a very crude and largely untested API endpoint for document merging #335 2021-03-11 17:58:16 +01:00
jonaswinkler 321869edb0 Merge branch 'dev' into feature-merge-tool 2021-03-11 10:10:31 +01: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 c6e8faa718 initial implementation of merge tool 2021-03-10 22:26:31 +01:00
jonaswinkler 6696c4d7a5 initial work on merge tool frontend 2021-03-10 21:43:14 +01:00
jonaswinkler 88254bdb79 Merge branch 'dev' into feature-merge-tool 2021-03-10 18:44:55 +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
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
Michael Shamoon 706b1f4622 Remove ng2-pdf-viewer and use only native viewer 2021-02-24 11:26:39 -08:00
jonaswinkler 4cc6bf889e Added a dummy endpoint with no functionality to the API 2021-02-13 14:56:58 +01: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
172 changed files with 43171 additions and 7890 deletions

No files matched your search

+25 -25
View File
@@ -12,30 +12,38 @@ FROM python:3.7-slim
# Binary dependencies
RUN apt-get update \
&& apt-get -y --no-install-recommends install \
&& 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 \
tzdata \
gosu \
# fonts for text file thumbnail generation
fonts-liberation \
# for Numpy
libatlas-base-dev \
libxslt1-dev \
mime-support \
# thumbnail size reduction
optipng \
sudo \
tzdata \
# OCRmyPDF dependencies
ghostscript \
icc-profiles-free \
liblept5 \
libxml2 \
pngquant \
unpaper \
zlib1g \
ghostscript \
icc-profiles-free \
&& 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 \
# fixes sudo / gosu issues
libseccomp2 \
# Mime type detection
file \
libmagic-dev \
media-types \
# OCRmyPDF dependencies
liblept5 \
qpdf \
tesseract-ocr \
tesseract-ocr-eng \
@@ -43,16 +51,7 @@ RUN apt-get update \
tesseract-ocr-fra \
tesseract-ocr-ita \
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/
@@ -83,6 +82,7 @@ RUN cd docker \
&& mkdir /var/log/supervisord /var/run/supervisord \
&& cp supervisord.conf /etc/supervisord.conf \
&& cp docker-entrypoint.sh /sbin/docker-entrypoint.sh \
&& cp docker-prepare.sh /sbin/docker-prepare.sh \
&& chmod 755 /sbin/docker-entrypoint.sh \
&& chmod +x install_management_commands.sh \
&& ./install_management_commands.sh \
@@ -98,8 +98,8 @@ COPY src/ ./
RUN addgroup --gid 1000 paperless \
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \
&& chown -R paperless:paperless ../ \
&& sudo -HEu paperless python3 manage.py collectstatic --clear --no-input \
&& sudo -HEu paperless python3 manage.py compilemessages
&& gosu paperless python3 manage.py collectstatic --clear --no-input \
&& gosu paperless python3 manage.py compilemessages
VOLUME ["/usr/src/paperless/data", "/usr/src/paperless/media", "/usr/src/paperless/consume", "/usr/src/paperless/export"]
ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
+3 -5
View File
@@ -10,7 +10,7 @@ name = "piwheels"
[packages]
dateparser = "~=1.0.0"
django = "~=3.1.3"
django = "~=3.2"
django-cors-headers = "*"
django-extensions = "*"
django-filter = "~=2.4.0"
@@ -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,7 +51,6 @@ 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.4"
"pdfminer.six" = "*"
Generated
+430 -440
View File
File diff suppressed because it is too large. Load diff
+12 -13
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.
@@ -57,19 +60,17 @@ Here's what you get:
* 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, French, and Portuguese.
There's an active translation project at transifex! 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.
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
+1
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
@@ -0,0 +1,94 @@
# docker-compose file for running paperless from the Docker Hub.
# This file contains everything paperless needs to run.
# Paperless supports amd64, arm and arm64 hardware.
#
# All compose files of paperless configure paperless in the following way:
#
# - Paperless is (re)started on system boot, if it was running before shutdown.
# - Docker volumes for storing data are managed by Docker.
# - Folders for importing and exporting files are created in the same directory
# as this file and mounted to the correct folders inside the container.
# - Paperless listens on port 8010.
#
# In addition to that, this docker-compose file adds the following optional
# configurations:
#
# - Instead of SQLite (default), PostgreSQL is used as the database server.
#
# To install and update paperless with this file, do the following:
#
# - Open portainer Stacks list and click 'Add stack'
# - Paste the contents of this file and assign a name, e.g. 'Paperless'
# - Click 'Deploy the stack' and wait for it to be deployed
# - Open the list of containers, select paperless_webserver_1
# - Click 'Console' and then 'Connect' to open the command line inside the container
# - Run 'python3 manage.py createsuperuser' to create a user
# - Exit the console
#
# For more extensive installation and update instructions, refer to the
# documentation.
version: "3.4"
services:
broker:
image: redis:6.0
restart: unless-stopped
db:
image: postgres:13
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless
webserver:
image: jonaswinkler/paperless-ng:latest
restart: unless-stopped
depends_on:
- db
- broker
ports:
- 8010:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
- ./export:/usr/src/paperless/export
- ./consume:/usr/src/paperless/consume
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
# The UID and GID of the user used to run paperless in the container. Set this
# to your UID and GID on the host so that you have write access to the
# consumption directory.
USERMAP_UID: 1000
USERMAP_GID: 100
# Additional languages to install for text recognition, separated by a
# whitespace. Note that this is
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
# language used for OCR.
# The container installs English, German, Italian, Spanish and French by
# default.
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
# for available languages.
#PAPERLESS_OCR_LANGUAGES: tur ces
# Adjust this key if you plan to make paperless available publicly. It should
# be a very long sequence of random characters. You don't need to remember it.
#PAPERLESS_SECRET_KEY: change-me
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
#PAPERLESS_TIME_ZONE: America/Los_Angeles
# The default language to use for OCR. Set this to the language most of your
# documents are written in.
#PAPERLESS_OCR_LANGUAGE: eng
volumes:
data:
media:
pgdata:
Regular → Executable
+4 -57
View File
@@ -15,59 +15,6 @@ map_uidgid() {
fi
}
wait_for_postgres() {
attempt_num=1
max_attempts=5
echo "Waiting for PostgreSQL to start..."
host="${PAPERLESS_DBHOST}"
port="${PAPERLESS_DBPORT}"
if [[ -z $port ]] ;
then
port="5432"
fi
while !</dev/tcp/$host/$port ;
do
if [ $attempt_num -eq $max_attempts ]
then
echo "Unable to connect to database."
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(expr "$attempt_num" + 1)
sleep 5
done
}
migrations() {
if [[ -n "${PAPERLESS_DBHOST}" ]]
then
wait_for_postgres
fi
(
# flock is in place to prevent multiple containers from doing migrations
# simultaneously. This also ensures that the db is ready when the command
# of the current container starts.
flock 200
echo "Apply database migrations..."
sudo -HEu paperless python3 manage.py migrate
) 200>/usr/src/paperless/data/migration_lock
}
initialize() {
map_uidgid
@@ -82,11 +29,12 @@ initialize() {
echo "creating directory /tmp/paperless"
mkdir -p /tmp/paperless
set +e
chown -R paperless:paperless ../
chown -R paperless:paperless /tmp/paperless
set -e
migrations
gosu paperless /sbin/docker-prepare.sh
}
install_languages() {
@@ -137,9 +85,8 @@ initialize
if [[ "$1" != "/"* ]]; then
echo Executing management command "$@"
exec sudo -HEu paperless python3 manage.py "$@"
exec gosu paperless python3 manage.py "$@"
else
echo Executing "$@"
exec "$@"
fi
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
wait_for_postgres() {
attempt_num=1
max_attempts=5
echo "Waiting for PostgreSQL to start..."
host="${PAPERLESS_DBHOST}"
port="${PAPERLESS_DBPORT}"
if [[ -z $port ]]; then
port="5432"
fi
while ! </dev/tcp/$host/$port; do
if [ $attempt_num -eq $max_attempts ]; then
echo "Unable to connect to database."
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(expr "$attempt_num" + 1)
sleep 5
done
}
migrations() {
(
# flock is in place to prevent multiple containers from doing migrations
# simultaneously. This also ensures that the db is ready when the command
# of the current container starts.
flock 200
echo "Apply database migrations..."
python3 manage.py migrate
) 200>/usr/src/paperless/data/migration_lock
}
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..."
python3 manage.py document_index reindex
echo $index_version | tee $index_version_file >/dev/null
fi
}
superuser() {
if [[ -n "${PAPERLESS_ADMIN_USER}" ]]; then
python3 manage.py manage_superuser
fi
}
do_work() {
if [[ -n "${PAPERLESS_DBHOST}" ]]; then
wait_for_postgres
fi
migrations
search_index
superuser
}
do_work
+1 -1
View File
@@ -1,4 +1,4 @@
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails document_sanity_checker;
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails document_sanity_checker manage_superuser;
do
echo "installing $command..."
sed "s/management_command/$command/g" management_script.sh > /usr/local/bin/$command
Regular → Executable
+1 -1
View File
@@ -6,7 +6,7 @@ cd /usr/src/paperless/src/
if [[ $(id -u) == 0 ]] ;
then
sudo -HEu paperless python3 manage.py management_command "$@"
gosu paperless python3 manage.py management_command "$@"
elif [[ $(id -un) == "paperless" ]] ;
then
python3 manage.py management_command "$@"
+3 -1
View File
@@ -193,7 +193,9 @@ This table lists the compatible versions for each database migration number.
+------------------+-----------------+
| 1012 | 1.1.0 - 1.2.1 |
+------------------+-----------------+
| 1013 | 1.3.0 - current |
| 1014 | 1.3.0 - 1.3.1 |
+------------------+-----------------+
| 1016 | 1.3.2 - current |
+------------------+-----------------+
Execute the following management command to migrate your database:
+7 -8
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
+38 -74
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/``
=============================
+120 -18
View File
@@ -5,25 +5,135 @@
Changelog
*********
paperless-ng 1.4.2
##################
* Fixed an issue with ``sudo`` that caused paperless to not start on many Raspberry Pi devices. Thank you `WhiteHatTux`_!
paperless-ng 1.4.1
##################
* Added Polish locale.
* Changed some parts of the Dockerfile to hopefully restore functionality on certain ARM devices.
* Updated python dependencies.
* `Michael Shamoon`_ added a sticky filter / bulk edit bar.
* `sbrl`_ changed the docker-entrypoint.sh script to increase compatibility with NFS shares.
* `Chris Nagy`_ added support for creating a super user by passing ``PAPERLESS_ADMIN_USER`` and
``PAPERLESS_ADMIN_PASSWORD`` as environment variables to the docker container.
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.
* Added a color picker for tag colors.
* Changes
* Added translations into Italian and Romanian. Thank you!
* 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.
* Close individual documents from the sidebar.
* Added a color picker for tag colors.
* `BolkoSchreiber <https://github.com/BolkoSchreiber>`_ added an option to disable/enable thumbnail inversion in dark mode.
* Added the ability to use the filter for searching the document content as well.
* Fixed an issue with the auto matching algorithm when more than 256 tags were used.
* Added translations into Italian and Romanian. Thank you!
* `Simon Taddiken <https://github.com/skuzzle>`_ added the ability to customize the header used for remote user authentication with SSO applications.
* Close individual documents from the sidebar. Thanks to `Michael Shamoon`_.
* The REST API is versioned from this point onwards. See the documentation about :ref:`api-versioning` for details.
* `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
@@ -63,17 +173,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
##################
@@ -1267,6 +1366,9 @@ bulk of the work on this big change.
* Initial release
.. _WhiteHatTux: https://github.com/WhiteHatTux
.. _Chris Nagy: https://github.com/what-name
.. _sbrl: https://github.com/sbrl
.. _slorenz: https://github.com/sisao
.. _Jo Vandeginste: https://github.com/jovandeginste
.. _zjean: https://github.com/zjean
+24
View File
@@ -177,6 +177,30 @@ PAPERLESS_AUTO_LOGIN_USERNAME=<username>
Defaults to none, which disables this feature.
PAPERLESS_ADMIN_USER=<username>
If this environment variable is specified, Paperless automatically creates
a superuser with the provided username at start. This is useful in cases
where you can not run the `createsuperuser` command seperately, such as Kubernetes
or AWS ECS.
Requires `PAPERLESS_ADMIN_PASSWORD` to be set.
.. note::
This will not change an existing [super]user's password, nor will
it recreate a user that already exists. You can leave this throughout
the lifecycle of the containers.
PAPERLESS_ADMIN_MAIL=<email>
(Optional) Specify superuser email address. Only used when
`PAPERLESS_ADMIN_USER` is set.
Defaults to ``root@localhost``.
PAPERLESS_ADMIN_PASSWORD=<password>
Only used when `PAPERLESS_ADMIN_USER` is set.
This will be the password of the automatically created superuser.
PAPERLESS_COOKIE_PREFIX=<str>
Specify a prefix that is added to the cookies used by paperless to identify
+18 -1
View File
@@ -174,7 +174,7 @@ Front end localization
* 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/" fodler and adjusting a couple files.
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":
@@ -269,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
===================
+47 -29
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
+11 -17
View File
@@ -460,6 +460,7 @@ Install Paperless using ansible
.. note::
This role currently only supports Debian 10 Buster and Ubuntu 20.04 Focal or later as target hosts.
Additionally, only i386 or amd64 based hosts are supported right now, i.e. installation on arm hosts will fail.
1. Install ansible 2.7+ on the management node.
This may be the target host paperless-ng is being installed on or any remote host which can access the target host.
@@ -485,29 +486,22 @@ Install Paperless using ansible
ansible -m ping YourAnsibleTargetHostGoesHere
2. Clone the repository of paperless-ng:
2. Install the latest tag of the ansible role using ansible-galaxy
.. code:: sh
git clone https://github.com/jonaswinkler/paperless-ng
ansible-galaxy install git+https://github.com/jonaswinkler/paperless-ng.git,ng-1.4.2
Checkout the latest release tag:
.. code:: sh
cd paperless-ng
git checkout ng-1.0.0
3. Create an ansible ``playbook.yml`` in the paperless-ng root directory:
3. Create an ansible ``playbook.yml`` in a directory of your choice:
.. code:: yaml
- hosts: YourAnsibleTargetHostGoesHere
become: yes
vars_files:
- ansible/vars.yml
- vars/paperless-ng.yml
roles:
- ansible
- paperless-ng
Optional: If you also want to use PostgreSQL on the target system, install and add (for example) the `geerlingguy.postgresql <https://github.com/geerlingguy/ansible-role-postgresql>`_ role:
@@ -520,10 +514,10 @@ Install Paperless using ansible
- hosts: YourAnsibleTargetHostGoesHere
become: yes
vars_files:
- ansible/vars.yml
- vars/paperless-ng.yml
roles:
- geerlingguy.postgresql
- ansible
- paperless-ng
Optional: If you also want to use a reverse proxy on the target system, install and add (for example) the `geerlingguy.nginx <https://github.com/geerlingguy/ansible-role-nginx>`_ role:
@@ -536,13 +530,13 @@ Install Paperless using ansible
- hosts: YourAnsibleTargetHostGoesHere
become: yes
vars_files:
- ansible/vars.yml
- vars/paperless-ng.yml
roles:
- geerlingguy.postgresql
- ansible
- paperless-ng
- geerlingguy.nginx
4. Create ``ansible/vars.yml`` to configure your ansible deployment:
4. Create ``vars/paperless-ng.yml`` to configure your ansible deployment:
.. code:: yaml
+13
View File
@@ -208,3 +208,16 @@ This might have multiple reasons.
SENDFILE=0
to your `docker-compose.env` file.
Error while reading metadata
############################
You might find messages like these in your log files:
.. code::
[WARNING] [paperless.parsing.tesseract] Error while reading metadata
This indicates that paperless failed to read PDF metadata from one of your documents. This happens when you
open the affected documents in paperless for editing. Paperless will continue to work, and will simply not
show the invalid metadata.
+2
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
#########
+25 -2
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_TIME_ZONE=$TIME_ZONE"
echo "PAPERLESS_OCR_LANGUAGE=$OCR_LANGUAGE"
echo "PAPERLESS_SECRET_KEY=$SECRET_KEY"
if [[ ! " ${DEFAULT_LANGUAGES[@]} " =~ " ${OCR_LANGUAGE} " ]] ; then
+25 -25
View File
@@ -8,11 +8,11 @@
-i https://pypi.python.org/simple
--extra-index-url https://www.piwheels.org/simple
aioredis==1.3.1
arrow==1.0.1; python_version >= '3.6'
asgiref==3.3.1; python_version >= '3.5'
arrow==1.0.3; python_version >= '3.6'
asgiref==3.3.4; python_version >= '3.6'
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.2; python_version >= '3.7'
autobahn==21.3.1; python_version >= '3.7'
automat==20.2.0
blessed==1.18.0
certifi==2020.12.5
@@ -24,57 +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.4.6
daphne==3.0.1; python_version >= '3.6'
cryptography==3.4.7
daphne==3.0.2; python_version >= '3.6'
dateparser==1.0.0
django-cors-headers==3.7.0
django-extensions==3.1.1
django-extensions==3.1.2
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
django==3.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.38.1
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.7.0
pathvalidate==2.3.2
ocrmypdf==11.7.3
pathvalidate==2.4.1
pdfminer.six==20201018
pikepdf==2.5.2
pillow==8.1.0
pikepdf==2.11.1
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'
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.17.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.4.4
reportlab==3.5.67; 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
@@ -84,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.58.0
tqdm==4.60.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
@@ -97,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.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+1 -1
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
+11 -5
View File
@@ -16,13 +16,18 @@
"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",
"fr-FR": "src/locale/messages.fr_FR.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"it": "src/locale/messages.it.xlf",
"ro": "src/locale/messages.ro.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",
"pl-PL": "src/locale/messages.pl_PL.xlf",
"sv-SE": "src/locale/messages.sv_SE.xlf"
}
},
"architect": {
@@ -104,7 +109,8 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
"browserTarget": "paperless-ui:build",
"ivy": true
}
},
"test": {
+133 -105
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">206</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">207</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">208</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">210</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">217</context>
</context-group>
</trans-unit>
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" 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">
@@ -1039,95 +997,123 @@
<context context-type="linenumber">106</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">73</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">74</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">32</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">34</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">39</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">41</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">45</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">49</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">53</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">88</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">89</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">90</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">91</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">94</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">20</context>
<context context-type="linenumber">19</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">28</context>
<context context-type="linenumber">27</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">35</context>
<context context-type="linenumber">34</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">58</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="7593728289020204896" datatype="html">
@@ -1198,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">
@@ -1226,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">87</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">
@@ -1418,7 +1411,7 @@
<source>Suggestions:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">26</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="27d158b47717ff9305d19866960418c603f19d55" datatype="html">
@@ -1626,6 +1619,13 @@
<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">
@@ -1682,32 +1682,67 @@
<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">95</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">96</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">97</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="792060551707690640" datatype="html">
<source>Polish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">101</context>
</context-group>
</trans-unit>
<trans-unit id="499386805970351976" datatype="html">
<source>Swedish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">102</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">102</context>
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit id="2119857572761283468" datatype="html">
@@ -1819,13 +1854,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">
+15
View File
@@ -8361,6 +8361,21 @@
"tslib": "^2.0.0"
}
},
"ngx-drag-drop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-drag-drop/-/ngx-drag-drop-2.0.0.tgz",
"integrity": "sha512-t+4/eiC8zaXKqU1ruNfFEfGs1GpMNwpffD0baopvZFKjQHCb5rhNqFilJ54wO4T0OwGp4/RnsVhlcxe1mX6UJg==",
"requires": {
"tslib": "^1.9.0"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"ngx-file-drop": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-10.0.0.tgz",
+1
View File
@@ -28,6 +28,7 @@
"ng2-pdf-viewer": "^6.3.2",
"ngx-color": "^6.2.0",
"ngx-cookie-service": "^10.1.1",
"ngx-drag-drop": "^2.0.0",
"ngx-file-drop": "^10.0.0",
"ngx-infinite-scroll": "^9.1.0",
"rxjs": "~6.6.0",
+6 -4
View File
@@ -10,23 +10,25 @@ 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";
import { SplitMergeComponent } from './components/split-merge/split-merge.component';
const routes: Routes = [
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
{path: '', component: AppFrameComponent, children: [
{path: 'dashboard', component: DashboardComponent },
{path: 'documents', component: DocumentListComponent },
{path: 'split_merge', component: SplitMergeComponent },
{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'}
+23 -6
View File
@@ -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';
@@ -35,6 +33,7 @@ import { DocumentCardSmallComponent } from './components/document-list/document-
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component';
import { NgxFileDropModule } from 'ngx-file-drop';
import { TextComponent } from './components/common/input/text/text.component';
import { InputDebounceComponent } from './components/common/input/debounce/input-debounce.component';
import { SelectComponent } from './components/common/input/select/select.component';
import { CheckComponent } from './components/common/input/check/check.component';
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
@@ -65,6 +64,11 @@ 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 { DocumentAsnComponent } from './components/document-asn/document-asn.component';
import { SplitMergeComponent } from './components/split-merge/split-merge.component';
import { DndModule } from 'ngx-drag-drop';
import { DocumentChooserComponent } from './components/common/document-chooser/document-chooser.component';
import { PageChooserComponent } from './components/common/page-chooser/page-chooser.component';
import localeFr from '@angular/common/locales/fr';
import localeNl from '@angular/common/locales/nl';
@@ -73,15 +77,24 @@ 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 localePl from '@angular/common/locales/pl';
import localeSv from '@angular/common/locales/sv';
registerLocaleData(localeFr)
registerLocaleData(localeNl)
registerLocaleData(localeDe)
registerLocaleData(localePt, "pt-BR")
registerLocaleData(localePt, "pt-PT")
registerLocaleData(localeIt)
registerLocaleData(localeEnGb)
registerLocaleData(localeRo)
registerLocaleData(localeRu)
registerLocaleData(localeEs)
registerLocaleData(localePl)
registerLocaleData(localeSv)
@NgModule({
declarations: [
@@ -100,8 +113,6 @@ registerLocaleData(localeRo)
TagEditDialogComponent,
DocumentTypeEditDialogComponent,
TagComponent,
SearchComponent,
ResultHighlightComponent,
PageHeaderComponent,
AppFrameComponent,
ToastsComponent,
@@ -113,6 +124,7 @@ registerLocaleData(localeRo)
DocumentCardSmallComponent,
BulkEditorComponent,
TextComponent,
InputDebounceComponent,
SelectComponent,
CheckComponent,
SaveViewConfigDialogComponent,
@@ -133,7 +145,11 @@ registerLocaleData(localeRo)
SafePipe,
CustomDatePipe,
DateComponent,
ColorComponent
ColorComponent,
DocumentAsnComponent,
SplitMergeComponent,
DocumentChooserComponent,
PageChooserComponent
],
imports: [
BrowserModule,
@@ -146,7 +162,8 @@ registerLocaleData(localeRo)
InfiniteScrollModule,
PdfViewerModule,
NgSelectModule,
ColorSliderModule
ColorSliderModule,
DndModule
],
providers: [
DatePipe,
@@ -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()">
@@ -68,6 +68,13 @@
</svg>&nbsp;<ng-container i18n>Documents</ng-container>
</a>
</li>
<li class="nav-item" *ngIf="splitMergeToolVisible">
<a class="nav-link" routerLink="split_merge" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors"/>
</svg>&nbsp;<ng-container i18n>Split & merge</ng-container>
</a>
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.sidebarViews.length > 0'>
@@ -99,7 +106,7 @@
</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"/>
@@ -175,7 +182,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>
@@ -10,6 +10,9 @@ 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';
import { SplitMergeService } from 'src/app/services/split-merge.service';
@Component({
selector: 'app-app-frame',
@@ -24,7 +27,9 @@ export class AppFrameComponent implements OnInit {
private openDocumentsService: OpenDocumentsService,
private searchService: SearchService,
public savedViewService: SavedViewService,
private meta: Meta
private list: DocumentListViewService,
private meta: Meta,
private splitMergeService: SplitMergeService
) {
}
@@ -74,7 +79,7 @@ export class AppFrameComponent implements OnInit {
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) {
@@ -103,6 +108,10 @@ export class AppFrameComponent implements OnInit {
}
}
get splitMergeToolVisible() {
return this.splitMergeService.hasDocuments()
}
ngOnInit() {
}
@@ -0,0 +1,15 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title" i18n>Choose Documents</h4>
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="m-n2 row row-cols-paperless-cards">
<app-document-card-small [simpleCard]="true" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents"></app-document-card-small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-outline-primary" [disabled]="list.selected.size == 0" (click)="confirmClicked.emit()" i18n>Add Selected</button>
</div>
@@ -0,0 +1,28 @@
@import "/src/theme";
.modal-body {
max-height: 70vh;
overflow-y: scroll;
}
$paperless-card-breakpoints: (
0: 2, // xs
768px: 3, //md
992px: 4, //lg
1200px: 5, //xl
1400px: 6, // xxl
1600px: 7,
1800px: 8,
2000px: 9
);
.row-cols-paperless-cards {
@each $width, $n_cols in $paperless-card-breakpoints {
@media(min-width: $width) {
> * {
flex: 0 0 auto;
width: 100% / $n_cols;
}
}
}
}
@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResultHighlightComponent } from './result-highlight.component';
import { DocumentChooserComponent } from './document-chooser.component';
describe('ResultHighlightComponent', () => {
let component: ResultHighlightComponent;
let fixture: ComponentFixture<ResultHighlightComponent>;
describe('DocumentChooserComponent', () => {
let component: DocumentChooserComponent;
let fixture: ComponentFixture<DocumentChooserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ResultHighlightComponent ]
declarations: [ DocumentChooserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ResultHighlightComponent);
fixture = TestBed.createComponent(DocumentChooserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -0,0 +1,36 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
@Component({
selector: 'app-document-chooser',
templateUrl: './document-chooser.component.html',
styleUrls: ['./document-chooser.component.scss']
})
export class DocumentChooserComponent implements OnInit {
constructor(
public activeModal: NgbActiveModal,
public list: DocumentListViewService
) { }
@Output()
public confirmClicked = new EventEmitter()
ngOnInit(): void {
this.list.selectNone()
this.list.activateSavedView(null)
this.list.reload()
}
cancelClicked() {
this.list.selectNone()
this.activeModal.close()
}
toggleSelected(d: PaperlessDocument, event: MouseEvent): void {
if (!event.shiftKey) this.list.toggleSelected(d)
else this.list.selectRangeTo(d)
}
}
@@ -0,0 +1,2 @@
<input type="text" [class]="cssClasses" [placeholder]="placeholder" [(ngModel)]="inputValue" (keyup)="keyUp($event)" />
<span *ngIf="allowClear && inputValue?.length > 0" class="clear-wrapper" title="Clear all" (click)="clear()"><span aria-hidden="true" class="clear">×</span></span>
@@ -0,0 +1,23 @@
.clear-wrapper {
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
cursor: pointer;
position: absolute;
top: 5px;
right: 5px;
user-select: none;
width: 17px;
color: #999;
.clear {
display: inline-block;
font-size: 18px;
line-height: 1;
pointer-events: none;
}
&:hover .clear {
color: #D0021B;
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputDebounceComponent } from './text.component';
describe('InputDebounceComponent', () => {
let component: InputDebounceComponent;
let fixture: ComponentFixture<InputDebounceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InputDebounceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InputDebounceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,66 @@
import { Component, Input, Output, ElementRef, EventEmitter } from '@angular/core';
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-input-debounce',
templateUrl: './input-debounce.component.html',
styleUrls: ['./input-debounce.component.scss']
})
export class InputDebounceComponent {
@Input()
placeholder: string
@Input()
delay: number = 500
@Input()
classes: string
@Input()
allowClear: boolean = true
@Input()
set pattern(regexStr: string) {
this._pattern = new RegExp(regexStr)
}
_pattern: RegExp
@Output()
value: EventEmitter<string> = new EventEmitter<string>()
@Input()
inputValue: string;
constructor(private elementRef: ElementRef) {
fromEvent(elementRef.nativeElement, 'keyup').pipe(
map(() => this.inputValue),
debounceTime(this.delay),
distinctUntilChanged()
).subscribe(input =>
this.value.emit(input)
);
}
keyUp(event: any) {
if (!this._pattern) return
if (event.key.length == 1 && !this._pattern.test(event.key)) { // e.g. dont do for backspace
// invalid character, prevent input
event.preventDefault()
event.stopImmediatePropagation()
this.inputValue = this.inputValue.slice(this.inputValue.length, 1)
}
}
get cssClasses(): string {
return 'form-control ' + this.classes
}
clear() {
this.inputValue = ''
this.value.emit(this.inputValue)
}
}
@@ -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>
@@ -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);
}
}
@@ -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>
@@ -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);
}
}
@@ -0,0 +1,13 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}: {{document.title}}</h4>
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pdf-viewer class="page-chooser-viewer" [class.splitting]="splitting" [src]="previewUrl" [original-size]="false" [zoom]=".25" [autoresize]="true" [show-borders]="true" [show-all]="true" [render-text-mode]="2" (page-rendered)="afterPageRendered($event)" (after-load-complete)="pdfLoaded($event)"></pdf-viewer>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-outline-primary" (click)="confirmClicked()" [disabled]="pages.length == 0">{{title}}</button>
</div>
@@ -0,0 +1,89 @@
@import "src/theme";
.modal-body {
max-height: 70vh;
overflow-y: scroll;
}
::ng-deep .page-chooser-viewer {
min-height: 400px;
width: 100%;
.pdfViewer {
display: flex !important;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
.page:not(.disabled) {
margin: 2px 3% 2rem !important;
cursor: pointer;
.textLayer {
pointer-events: none;
}
&:hover {
.textLayer {
background-color: $primary !important;
opacity: 0.5;
}
}
}
}
&:not(.splitting) {
.pdfViewer {
.page.selected {
&::after {
position: absolute;
top: 0.5rem;
left: 0.5rem;
display: block;
width: 1rem;
height: 1rem;
border-radius: 0.25rem;
content: "";
background: $primary no-repeat 50%/50% 50%;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e");
}
.textLayer {
background-color: $primary !important;
opacity: 0.5;
}
}
}
}
&.splitting {
.pdfViewer {
.page:not(:last-child).selected::after,
.page:not(:last-child):not(.disabled):hover::after {
background-color: $primary;
width: 2rem;
height: 100%;
content: "Split Here";
color: #abb2bf; // dark mode text color
position: absolute;
top: 0;
right: -2rem;
writing-mode: vertical-lr;
padding: 0.5rem;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.1rem;
font-size: 80%;
font-weight: bold;
}
.page.disabled {
.canvasWrapper,
.textLayer {
opacity: 0.25;
pointer-events: none;
}
}
}
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PageChooserComponent } from './page-chooser.component';
describe('PageChooserComponent', () => {
let component: PageChooserComponent;
let fixture: ComponentFixture<PageChooserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PageChooserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageChooserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,90 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { PaperlessDocument, PaperlessDocumentPart } from 'src/app/data/paperless-document';
import { DocumentService } from 'src/app/services/rest/document.service';
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
@Component({
selector: 'app-page-chooser',
templateUrl: './page-chooser.component.html',
styleUrls: ['./page-chooser.component.scss']
})
export class PageChooserComponent implements OnInit {
constructor(
public activeModal: NgbActiveModal,
private documentService: DocumentService
) { }
get previewUrl(): string {
return this.documentService.getPreviewUrl(this.document.id)
}
@Input()
document: PaperlessDocument
@Input()
splitting: boolean = false
public pages: number[]
public enabledPages: number[]
private nPages: number
@Output()
public confirmPages = new EventEmitter<number[]>()
get title(): string {
return this.splitting ? 'Select Split' : 'Select Pages'
}
ngOnInit(): void {
this.pages = (this.document as PaperlessDocumentPart).pages
if (this.splitting && this.pages?.length) this.enabledPages = this.pages.concat([])
if (!this.pages) this.pages = []
}
cancelClicked() {
this.activeModal.close()
}
confirmClicked() {
if (this.splitting) {
// we need pages as the full range since we wont know length of document later
const startPage = this.pages.pop()
for (let page = startPage + 1; page <= this.nPages; page++) {
if (!this.enabledPages || (this.enabledPages && this.enabledPages.indexOf(page) !== -1)) this.pages.push(page)
}
}
this.confirmPages.emit(this.pages.sort((a, b) => { return a - b }))
}
pdfLoaded(pdf: PDFDocumentProxy) {
this.nPages = pdf.numPages
}
public afterPageRendered(pageRenderedEvent: any) {
const pageView = pageRenderedEvent.source /* as PDFPageView */
const div = pageView.div as HTMLDivElement
if (this.splitting && this.enabledPages?.length) {
div.classList.toggle('disabled', this.enabledPages.indexOf(parseInt(div.dataset.pageNumber)) == -1)
} else {
div.classList.toggle('selected', this.pages.indexOf(parseInt(div.dataset.pageNumber)) !== -1)
}
div.onclick = () => {
let pageNum: number = parseInt(div.dataset.pageNumber)
if (this.splitting) { // only select 1 page
if (pageNum == this.nPages || (this.enabledPages?.length && pageNum == Math.max(...this.enabledPages))) return
this.pages = [pageNum]
div.parentNode.childNodes.forEach(pageEl => {
(pageEl as Element).classList.toggle('selected', false)
})
div.classList.toggle('selected', true)
} else {
this.pages.indexOf(pageNum) !== -1 ? this.pages.splice(this.pages.indexOf(pageNum), 1) : this.pages.push(pageNum)
div.classList.toggle('selected', this.pages.indexOf(pageNum) !== -1)
}
}
}
}
@@ -1,5 +1,5 @@
<app-page-header title="Dashboard" [subTitle]="subtitle" i18n-title>
<svg class="logo d-none d-md-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.64" height="5rem">
<svg class="logo d-none d-md-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.64" height="6em">
<path class="leaf" d="M730.71,0C616.3,266.24,66.21,299.25,242.24,728.31c2.2,5.5-40.71,63.81-69.31,112.22-6.6-24.21-14.3-50.61-13.2-52.81C302.75,618.29,57.41,522.58,44.21,384-17.4,494-39.4,686.5,146.53,797.62c1.1,0,9.9,41.81,14.3,62.71-4.4,8.8-8.8,17.6-11,24.2-4.4,11,28.6,9.91,28.6,12.11,3.3-1.1,81.41-138.63,83.61-139.73C704.31,655.7,782.42,257.44,730.71,0ZM105.82,544.58C145.43,580.89,211.44,692,155.33,756.91,168.53,727.21,172.93,660.1,105.82,544.58ZM269.74,670C200.43,506.08,404,325.65,505.18,280.54,298.35,465.37,263.14,602.89,269.74,670Z" transform="translate(0)" style="fill:#17541f"/>
<path class="text" style="fill:#000" d="M1169.27,231.44q.7,17.06.71,38.73t-.71,38a112,112,0,0,1-6.76,38A100.16,100.16,0,0,1,1142.25,380a93.87,93.87,0,0,1-33.76,23.46q-20.25,8.54-46.56,8.53h-36.25V597.5q0,9.94,2.85,14.57c1.89,3.08,3.79,4.86,5.68,5.33a10.54,10.54,0,0,0,9.24,0v18.48h-86V617.4q5.68,2.13,8.53,0,3.56-.71,7.1-5.33t3.56-14.57V166q0-10.66-3.56-15.28t-7.1-6q-2.85-1.42-8.53,0V127.66h104.48q26.31,0,46.56,8.53a93.75,93.75,0,0,1,33.76,23.46,101,101,0,0,1,20.26,33.4A111.05,111.05,0,0,1,1169.27,231.44ZM1118.8,215.8q0-18.48-6-32t-14.22-21.32a74.68,74.68,0,0,0-17.06-12.44q-8.88-4.62-15.28-4.62h-40.52V392.79h40.52q6.39,0,15.28-3.56t17.06-12.44a86,86,0,0,0,14.22-21.67q6-12.8,6-32Zm337.63,389.52a27.35,27.35,0,0,1-2.85,11.72,40.58,40.58,0,0,1-8.17,11.38,54.8,54.8,0,0,1-13.5,9.59,40.07,40.07,0,0,1-18.84,4.27,60,60,0,0,1-30.57-7.82q-13.5-7.82-18.48-20.61a56.12,56.12,0,0,1-6.39-16.35q-9.24,19.18-28.08,32a72.42,72.42,0,0,1-41.58,12.8q-28.44,0-49.76-20.61-12.07-10.67-19.9-27.73t-9.24-39.09q-.71-2.13-.71-8.53v-.71a124.43,124.43,0,0,1,4.26-25.23,113.22,113.22,0,0,1,11-26.3,149.46,149.46,0,0,1,17.77-24.88,128.6,128.6,0,0,1,22.39-20.26,17.39,17.39,0,0,0,4.62-3.2,28.53,28.53,0,0,1,5.33-3.91l2.13-1.06,2.14-1.07q12.8-7.81,22.39-14.22t17.41-12.08a97.25,97.25,0,0,0,8.53-6.4c2.84-2.37,5.93-4.73,9.24-7.1a79.43,79.43,0,0,0,12.09-10,36.51,36.51,0,0,0,8.53-13.5q4.26-7.11,4.26-17.06a67.74,67.74,0,0,0-2.13-17.77,49.09,49.09,0,0,0-14.57-27q-11.73-11.37-26-14.93a38.26,38.26,0,0,0-12.08-2.13q-19.9,0-34.12,7.11-9.24,4.26-13.51,9.59t-4.62,10a10.73,10.73,0,0,0,1.78,7.46,12.91,12.91,0,0,0,5.69,4.26,6,6,0,0,0,1.78.71,6.28,6.28,0,0,1,1.77.72,32.21,32.21,0,0,1,15.28,12.43,34,34,0,0,1,6,19.55,32.78,32.78,0,0,1-32.7,32.7,33.53,33.53,0,0,1-24.17-9.6,30.94,30.94,0,0,1-10-23.1,106.83,106.83,0,0,1,3.2-25.23,84.65,84.65,0,0,1,9.6-23.81q13.5-19.91,32.34-28.79a96.24,96.24,0,0,1,41.58-8.89,93.82,93.82,0,0,1,33.76,6.4,96.18,96.18,0,0,1,29.86,18.13,103.38,103.38,0,0,1,22,28.07q8.88,16.35,8.88,37.68v209q0,12.79.36,16.7t.36,5.34h0q0,9.24,5.68,14.92a18.45,18.45,0,0,0,13.51,5.69q9.24,0,14.92-5.33a20.62,20.62,0,0,0,6.4-13.86Zm-111.6-17.77a52.58,52.58,0,0,0,6.76-21.33q1.07-11.37,1.06-21.32V413.4a63.92,63.92,0,0,1-6,5.33,74,74,0,0,0-6.75,6q-20.62,14.93-35.19,27.37a164.06,164.06,0,0,0-24.52,25.59,113,113,0,0,0-15.64,28.07,121,121,0,0,0-7.11,34.12v10.67q.72,27,14.58,42.29t39.44,15.28a38.56,38.56,0,0,0,20.62-5.69q9.24-6.39,12.79-14.92Zm244.52-304.23q40.51.72,64.68,24.53t24.17,75.7V542.77q0,51.88-24.17,75.34T1589.35,643h-2.13a80.27,80.27,0,0,1-22-2.84h-.71a.63.63,0,0,1-.71-.71,91.74,91.74,0,0,1-28.43-15.64l-6.76-6.76-5.33-7.46v118q0,10,2.85,14.58c1.89,3.08,3.79,5.09,5.68,6a10.54,10.54,0,0,0,9.24,0V766h-86V748.19q5.68,2.13,8.53,0,3.56-1.43,6.75-6t3.2-14.58V326.68q0-9.94-3.2-14.57t-6.75-6q-2.85-1.43-8.53,0V289h68.23v27.72a68.68,68.68,0,0,1,12.09-14.21,75.62,75.62,0,0,1,28.43-15.64.63.63,0,0,1,.71-.71h.71a90.55,90.55,0,0,1,22-2.85Zm35.54,84.59v-.71q-.72-36.26-8.89-51.18t-26.65-16.35a63.12,63.12,0,0,0-24.17,3.2,48,48,0,0,0-19.9,13.15q-8.54,9.24-14.22,24.88t-7.82,37V548.45q2.13,22,7.82,37.32t14.22,24.17a53.23,53.23,0,0,0,19.9,13.15,58.92,58.92,0,0,0,24.17,3.55q18.48-1.42,26.65-16.35t8.89-51.18V367.91Zm179.83-84.59a105.36,105.36,0,0,1,33.41,5.69,86.38,86.38,0,0,1,30.56,17.77,88.06,88.06,0,0,1,21.68,30.57q8.18,18.48,8.18,44.06v90.28H1757.1V544.9q0,44.07,15.28,62.9t38,18.13a113.94,113.94,0,0,0,26.65-4.62,54.83,54.83,0,0,0,22.75-13.15q9.59-9.24,16-26.3t6.4-44.78h16.35V547a153.56,153.56,0,0,1-4.27,36.61A72,72,0,0,1,1852,634.1q-16.71,7.47-40.87,8.18a102.25,102.25,0,0,1-34.83-6.4,96,96,0,0,1-32-19.19,108.91,108.91,0,0,1-23.45-30.21q-9.24-17.41-9.25-39.45v-16.7s-.11-9.12-.35-27.37-.36-29.5-.36-33.76-.11-16-.35-35.19-.36-29.49-.36-30.92v-21q0-25.59,8.89-44.43t22-30.56A90.72,90.72,0,0,1,1771,289.37a98.55,98.55,0,0,1,33.76-6.05Zm46.2,169.89V397.05q0-27-3.55-45.13t-9.6-29.15q-6-11-14.57-15.28a42.36,42.36,0,0,0-19.19-4.26q-22.76,0-34.48,19.9t-11.72,73.92v56.16Zm279.35-113.73a42.31,42.31,0,0,0,.71,5.68,17.92,17.92,0,0,1,0,7.11q-.72,14.93-10.31,24.52a33,33,0,0,1-46.91-.35,33.52,33.52,0,0,1-9.6-24.17q0-17.76,13.51-27a8.33,8.33,0,0,0,1.77-1.06l1.07-1.07a16.22,16.22,0,0,0,6.4-10.31q1.41-6.75-8.53-11.73a26.26,26.26,0,0,0-12.8-3.55q-21.32-1.43-37.67,12.8t-22,49.75V597.5q0,9.94,2.84,14.57t5.69,5.33a10.54,10.54,0,0,0,9.24,0v18.48h-86V617.4q5.68,2.13,8.53,0,3.55-.71,7.11-5.33t3.55-14.57V326.68q0-9.94-3.55-14.57t-7.11-6q-2.85-1.43-8.53,0V288.3h68.24v29.14q5-7.81,9.95-14.57a48.44,48.44,0,0,1,11.72-11.37,59.79,59.79,0,0,1,15.64-7.46q8.89-2.85,22.39-2.85,20.61,0,38.39,14.93t26.3,43.36Zm95.95,258q0,9.94,3.2,14.57c2.13,3.08,4.15,4.86,6,5.33q3.55,2.13,9.24,0v18.48h-86V617.4q5.68,2.13,10,0,2.13-.71,5.69-5.33t3.55-14.57V166.75q0-10.65-3.55-15.28a28.82,28.82,0,0,0-5.69-6q-4.26-2.13-10,0V128.37h67.52V597.5Zm154.25-314.18a105.25,105.25,0,0,1,33.4,5.69,86.33,86.33,0,0,1,30.57,17.77,88.19,88.19,0,0,1,21.68,30.57q8.18,18.48,8.17,44.06v90.28H2332.84V544.9q0,44.07,15.29,62.9t38,18.13a113.93,113.93,0,0,0,Line truncated
</svg>
@@ -19,6 +19,6 @@
<app-statistics-widget></app-statistics-widget>
<app-upload-file-widget></app-upload-file-widget>
</div>
</div>
@@ -4,11 +4,6 @@ form {
position: relative;
}
.alert-heading {
font-size: 80%;
font-weight: bold;
}
.alerts-hidden {
.btn {
line-height: 1;
@@ -23,7 +18,7 @@ form {
}
}
::ng-deep .progress {
.progress {
position: absolute;
top: 0;
right: 0;
@@ -0,0 +1 @@
<p i18n>Searching document with asn {{asn}}</p>
Whitespace-only changes.
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentAsnComponent } from './document-asn.component';
describe('DocumentASNComponentComponent', () => {
let component: DocumentAsnComponent;
let fixture: ComponentFixture<DocumentAsnComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DocumentAsnComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DocumentAsnComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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'])
}
})
})
}
}
@@ -38,6 +38,12 @@
</svg>&nbsp;<span class="d-none d-lg-inline" i18n>More like this</span>
</button>
<button type="button" class="btn btn-sm btn-outline-primary mr-2" (click)="splitMerge()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors" />
</svg>&nbsp;<span class="d-none d-lg-inline" i18n>Add to split/merge</span>
</button>
<button type="button" class="btn btn-sm btn-outline-primary" (click)="close()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x" />
@@ -60,9 +66,9 @@
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
<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>
@@ -139,7 +145,7 @@
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safe" type="application/pdf" class="preview-sticky" width="100%"></object>
<object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
@@ -20,6 +20,8 @@ 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';
import { SplitMergeService } from 'src/app/services/split-merge.service';
@Component({
selector: 'app-document-detail',
@@ -75,7 +77,8 @@ export class DocumentDetailComponent implements OnInit {
private documentListViewService: DocumentListViewService,
private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService,
private settings: SettingsService) { }
private settings: SettingsService,
private splitMergeService: SplitMergeService) { }
get useNativePdfViewer(): boolean {
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
@@ -127,9 +130,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 +142,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 +224,12 @@ 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()}])
}
splitMerge() {
this.splitMergeService.addDocument(this.document)
this.router.navigate(["split_merge"])
}
hasNext() {
@@ -56,6 +56,12 @@
</div>
</div>
<div class="col-auto ml-auto mb-2 mb-xl-0 d-flex">
<button type="button" class="btn btn-sm btn-outline-primary mr-2" (click)="addToSplitMerge()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors" />
</svg>&nbsp;<span class="d-none d-lg-inline" i18n>Add to split/merge</span>
</button>
<div class="btn-group btn-group-sm mr-2">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="downloadSelected()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, EventEmitter, Output } 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';
@@ -16,6 +16,8 @@ import { MatchingModel } from 'src/app/data/matching-model';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { ToastService } from 'src/app/services/toast.service';
import { saveAs } from 'file-saver';
import { SplitMergeService } from 'src/app/services/split-merge.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-bulk-editor',
@@ -41,7 +43,9 @@ export class BulkEditorComponent {
private modalService: NgbModal,
private openDocumentService: OpenDocumentsService,
private settings: SettingsService,
private toastService: ToastService
private toastService: ToastService,
private splitMergeService: SplitMergeService,
private router: Router
) { }
applyOnClose: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE)
@@ -214,4 +218,9 @@ export class BulkEditorComponent {
saveAs(result, 'documents.zip');
})
}
addToSplitMerge() {
this.splitMergeService.addDocuments(this.list.selectedDocuments)
this.router.navigate(["split_merge"])
}
}
@@ -1,4 +1,4 @@
<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" [class.inverted]="getIsThumbInverted()">
@@ -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,52 @@
<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>
<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>
<ngb-progressbar [type]="searchScoreClass" [value]="searchScore" class="search-score-bar mx-2" [max]="1"></ngb-progressbar>
<div *ngIf="document.__search_hit__" class="list-group-item bg-light text-dark border-0 d-flex p-0 pl-4 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>
@@ -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);
}
@@ -1,13 +1,16 @@
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 {
@@ -23,31 +26,35 @@ 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"
}
}
}
@@ -58,19 +65,6 @@ export class DocumentCardLargeComponent implements OnInit {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}
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
}
}
getThumbUrl() {
return this.documentService.getThumbUrl(this.document.id)
}
@@ -79,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)
}
}
@@ -1,5 +1,5 @@
<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" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()">
@@ -10,7 +10,7 @@
</div>
</div>
<div style="top: 0; right: 0; font-size: large" class="text-right position-absolute mr-1">
<div *ngIf="!simpleCard" style="top: 0; right: 0; font-size: large" class="text-right position-absolute mr-1">
<div *ngFor="let t of getTagsLimited$() | async">
<app-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Filter by tag" i18n-linkTitle></app-tag>
</div>
@@ -22,27 +22,63 @@
<div class="card-body p-2">
<p class="card-text">
<ng-container *ngIf="document.correspondent">
<ng-container *ngIf="document.correspondent && !simpleCard">
<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 *ngIf="!simpleCard" 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>
@@ -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%;
}
@@ -1,13 +1,14 @@
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 {
@@ -15,21 +16,32 @@ export class DocumentCardSmallComponent implements OnInit {
@Input()
selected = false
@Output()
toggleSelected = new EventEmitter()
@Input()
document: PaperlessDocument
@Input()
simpleCard: boolean = false
@Output()
toggleSelected = new EventEmitter()
@Output()
clickTag = new EventEmitter<number>()
@Output()
clickCorrespondent = new EventEmitter<number>()
@Output()
clickDocumentType = new EventEmitter<number>()
moreTags: number = null
@ViewChild('popover') popover: NgbPopover
mouseOnPreview = false
popoverHidden = true
ngOnInit(): void {
}
@@ -45,7 +57,7 @@ export class DocumentCardSmallComponent implements OnInit {
return this.documentService.getDownloadUrl(this.document.id)
}
getPreviewUrl() {
get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id)
}
@@ -62,4 +74,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()
}
}
@@ -75,8 +75,8 @@
</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>
<div class="sticky-top py-2 mt-n2 mt-sm-n3 py-sm-4 bg-body mx-n3 px-3">
<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>
@@ -34,3 +34,12 @@ $paperless-card-breakpoints: (
right: 0 !important;
left: auto !important;
}
.sticky-top {
z-index: 990; // below main navbar
top: calc(7rem - 2px); // height of navbar (mobile)
@media (min-width: 580px) {
top: 3.5rem; // height of navbar
}
}
@@ -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
}
@@ -1,7 +1,6 @@
<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>
<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>
@@ -9,7 +8,7 @@
<button *ngFor="let t of textFilterTargets" ngbDropdownItem [class.active]="textFilterTarget == t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
</div>
</div>
<input class="form-control form-control-sm" type="text" [(ngModel)]="textFilter">
<input #textFilterInput class="form-control form-control-sm" type="text" [(ngModel)]="textFilter" [readonly]="textFilterTarget == 'fulltext-morelike'">
</div>
</div>
</div>
@@ -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,12 +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, FILTER_TITLE_CONTENT } 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',
@@ -51,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}`
}
}
@@ -60,19 +68,33 @@ 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[] = []
_textFilter = ""
_moreLikeId: number
_moreLikeDoc: PaperlessDocument
textFilterTargets = [
{id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`},
{id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`}
]
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
@@ -90,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._textFilter = null
this._moreLikeId = null
this.dateAddedBefore = null
this.dateAddedAfter = null
this.dateCreatedBefore = null
@@ -111,6 +149,22 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
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
break
@@ -137,6 +191,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
break
}
})
this.checkIfRulesHaveChanged()
}
get filterRules(): FilterRule[] {
@@ -147,6 +202,15 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
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"})
} else {
@@ -178,12 +242,27 @@ 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)
}
@@ -211,8 +290,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
distinctUntilChanged()
).subscribe(text => {
this._textFilter = text
this.documentService.searchQuery = text
this.updateRules()
})
if (this._textFilter) this.documentService.searchQuery = this._textFilter
}
ngOnDestroy() {
@@ -220,7 +303,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
}
resetSelected() {
this.reset.next()
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
this.filterRules = this._unmodifiedFilterRules
this.updateRules()
}
toggleTag(tagId: number) {
@@ -248,7 +333,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
}
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()
}
}
@@ -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;
}
@@ -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>
@@ -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;
}
}
@@ -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'
});
}
}
@@ -111,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">
@@ -1,3 +0,0 @@
... <span *ngFor="let fragment of highlights">
<span *ngFor="let token of fragment" [class.match]="token.highlight">{{token.text}}</span> ...
</span>
@@ -1,4 +0,0 @@
.match {
color: black;
background-color: rgb(255, 211, 66);
}
@@ -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 {
}
}
@@ -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>
@@ -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;
}
@@ -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)
}
}
}
@@ -0,0 +1,166 @@
<app-page-header i18n-title title="Split & Merge Editor">
<div class="btn-group mt-2 mt-sm-0">
<button class="btn btn-sm btn-outline-primary" i18n-title title="Add Documents" (click)="chooseDocuments()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>&nbsp;<ng-container i18n>Add Documents</ng-container>
</button>
</div>
<div class="btn-group ml-2 mt-2 mt-sm-0">
<button type="button" class="btn btn-sm btn-outline-primary"
[popoverTitle]="optionsTitle" [autoClose]="'outside'" [ngbPopover]="optionsPopoverBody" popoverClass="shadow" #optionsPopover="ngbPopover">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear" />
</svg>&nbsp;<ng-container i18n>Options</ng-container>
</button>
<ng-template #optionsTitle>
<ng-container i18n>Options</ng-container>
<button type="button" class="close" aria-label="Close" (click)="optionsPopover.close()">
<span aria-hidden="true">&times;</span>
</button>
</ng-template>
<ng-template #optionsPopoverBody>
<div class="form-group row text-dark">
<label for="source_options" class="col-sm-3 col-form-label pt-0" i18n>Delete source documents</label>
<div class="col-sm-9">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="source_options-delete" [(ngModel)]="delete_source_documents">
<label class="custom-control-label" for="source_options-delete" [class.text-danger]="delete_source_documents"><ng-container i18n>Delete</ng-container><br/><small [class.text-muted]="!delete_source_documents" i18n>Source documents will be immediately deleted.</small></label>
</div>
</div>
</div>
<div class="form-group mt-4 row text-dark">
<label for="metadata_options" class="col-sm-3 col-form-label pt-0" i18n>Output document metadata</label>
<div class="col-sm-9">
<div class="custom-control custom-radio">
<input type="radio" id="metadata_setting-copy" name="metadata_setting" class="custom-control-input" [value]="splitMergeMetadata.COPY_FIRST" [(ngModel)]="metadata_setting">
<label class="custom-control-label" for="metadata_setting-copy"><ng-container i18n>First document</ng-container><br/><small class="text-muted" i18n>Metadata will be copied from first document.</small></label>
</div>
<div class="custom-control custom-radio">
<input type="radio" id="metadata_setting-redo" name="metadata_setting" class="custom-control-input" [value]="splitMergeMetadata.REDO" [(ngModel)]="metadata_setting">
<label class="custom-control-label" for="metadata_setting-redo"><ng-container i18n>Redo</ng-container><br/><small class="text-muted" i18n>Metadata detection will be re-run on new document.</small></label>
</div>
</div>
</div>
</ng-template>
</div>
<div class="btn-group ml-2 mr-2 mr-sm-5 mt-2 mt-sm-0">
<button class="btn btn-sm btn-outline-primary" i18n-title title="Save" (click)="save()" [disabled]="splitMergeService.getDocuments().length < 1">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check-circle" />
</svg>&nbsp;<ng-container i18n>Save</ng-container>
</button>
</div>
<div class="btn-group ml-sm-5 mt-2 mt-sm-0">
<button class="btn btn-sm btn-outline-danger" i18n-title title="Cancel" (click)="cancel()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#slash-circle" />
</svg>&nbsp;<ng-container i18n>Cancel</ng-container>
</button>
</div>
</app-page-header>
<div class="row">
<div class="col-md-6 col-lg-5">
<div class="h-100" dndDropzone (dndDrop)="onDrop($event, documents)">
<div class="spacer bg-primary" dndPlaceholderRef>
</div>
<div *ngFor="let d of documents; let i = index" class="card w-100 mb-3" [class.separator-card]="d.is_separator" [dndDraggable]="d" dndEffectAllowed="move" (dndMoved)="onDragged(d, documents)">
<ng-container *ngIf="d.is_separator">
<div class="separator"></div>
<h6 class="text-muted small font-weight-bold d-inline-block py-2 px-3 mb-0 mx-auto bg-body" i18n>Document End</h6>
<button class="btn btn-sm btn-danger" title="Remove" (click)="removeDocument(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>
</button>
</ng-container>
<div *ngIf="!d.is_separator" class="row no-gutters">
<div class="col-md-3 bg-light d-flex handle" dndHandle>
<svg width="2em" height="100%" viewBox="0 0 16 16" fill="currentColor" class="mx-auto">
<use xlink:href="assets/bootstrap-icons.svg#grip-horizontal" />
</svg>
<div class="doc-thumb" [style.background-image]="'url(' + getThumbUrl(d.id) + ')'"></div>
</div>
<div class="col-md-9">
<div class="card-body p-3">
<h6 class="card-title mb-2">{{d.title}}</h6>
<div class="card-actions">
<div class="btn-toolbar btn-toolbar-sm justify-content-between w-100">
<button class="btn btn-sm btn-outline-danger flex-fill" i18n-title title="Remove" (click)="removeDocument(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg><span class="d-none d-xl-inline">&nbsp;<ng-container i18n>Remove</ng-container></span>
</button>
<button class="btn btn-sm btn-outline-primary ml-3 flex-fill" i18n-title title="Split" (click)="chooseSplit(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors" />
</svg><span class="d-none d-xl-inline">&nbsp;<ng-container i18n>Split</ng-container></span>
</button>
</div>
<div class="input-group input-group-sm w-100 mt-3">
<div class="input-group-prepend">
<div class="input-group-text"><ng-container i18n>Pages</ng-container>:</div>
</div>
<app-input-debounce delay="500" classes="form-control-sm" placeholder="All" ngbTooltip="e.g. 1-3,5" pattern="[0-9\,\-]" [inputValue]="formatPages(d.pages)" (value)="pagesFieldChange($event, d, i)"></app-input-debounce>
<div class="input-group-append">
<button class="btn btn-outline-primary" type="button" i18n-title title="Select Pages" (click)="choosePages(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
</svg><span class="d-none d-xl-inline">&nbsp;<ng-container i18n>Select Pages</ng-container></span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-7">
<ngb-alert *ngIf="error" type="danger" class="mt-2 mb-0" [dismissible]="false">
<h6 class="alert-heading" i18n>An error occurred during operation</h6>
<p class="mb-0 pb-1" i18n>Please check document settings and parts.</p>
<p class="mb-0 pb-1 small"><ng-container i18n>Message</ng-container>: <span class="font-italic">{{error}}</span></p>
</ngb-alert>
<ng-container *ngIf="!error && previewUrls.length > 1">
<ul ngbNav #previewNav="ngbNav" class="nav-tabs">
<li *ngFor="let previewUrl of previewUrls; let j = index" [ngbNavItem]="j">
<a class="bg-light" ngbNavLink><ng-container i18n>Document</ng-container> {{j + 1}}</a>
<ng-template ngbNavContent>
<div class="preview-container px-2 tabbed text-center bg-light">
<ng-container [ngTemplateOutlet]="previewHeader" [ngTemplateOutletContext]="{ $implicit: j }"></ng-container>
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2" [(page)]="previewCurrentPages[j]" (after-load-complete)="pdfPreviewLoaded($event, j)" (page-rendered)="pdfPageRendered($event, j)"></pdf-viewer>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="previewNav"></div>
</ng-container>
<div *ngIf="!error && previewUrls.length <= 1" class="preview-container px-2 text-center bg-light">
<ng-container [ngTemplateOutlet]="previewHeader" [ngTemplateOutletContext]="{ $implicit: 0 }"></ng-container>
<pdf-viewer *ngIf="previewUrls.length > 0" [src]="previewUrls[0]" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2" [(page)]="previewCurrentPages[0]" (after-load-complete)="pdfPreviewLoaded($event, 0)" (page-rendered)="pdfPageRendered($event, 0)"></pdf-viewer>
</div>
</div>
</div>
<ng-template #previewHeader let-index>
<div class="d-flex mt-2 p-2 w-100 justify-space-between">
<h6 class="text-muted mt-1" [class.mx-auto]="previewNumPages[index] == undefined" [class.mr-auto]="previewNumPages[index] !== undefined" i18n>Preview</h6>
<div *ngIf="previewNumPages[index] !== undefined" class="input-group input-group-sm w-auto">
<div class="input-group-prepend">
<div class="input-group-text" i18n>Page</div>
</div>
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages[index]" [(ngModel)]="previewCurrentPages[index]" />
<div class="input-group-append">
<div class="input-group-text" i18n>of {{previewNumPages[index]}}</div>
</div>
</div>
</div>
<div *ngIf="loading" class="spinner-border" role="status">
<span class="sr-only" i18n>Loading...</span>
</div>
</ng-template>
@@ -0,0 +1,116 @@
@import "/src/theme";
.card {
.handle {
cursor: move;
}
.doc-thumb {
background-position: top center;
background-size: cover;
background-repeat: no-repeat;
min-height: 5rem;
height: 100%;
width: 70%;
}
&.separator-card {
border: none;
background: transparent;
}
}
.spacer {
min-height: 0.25rem;
width: 100%;
margin: 1rem;
}
.preview-container {
height: calc(100vh - 170px);
overflow: hidden;
&.tabbed {
height: calc(100vh - 200px);
}
.spinner-border {
margin-top: 40%;
}
}
.split-document-preview ::ng-deep .ng2-pdf-viewer-container {
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
}
::ng-deep .document-selector {
width: 80vw;
min-width: 50vw;
}
button span {
margin-left: 0.15rem;
}
app-input-debounce {
flex: 1 1;
position: relative;
::ng-deep .form-control {
border-radius: 0;
}
}
.nav-link.bg-light {
border-bottom-color: #f8f9fa;
&:not(.active) {
background-color: #fff !important;
border-bottom-color: #dee2e6;
}
}
.separator-card {
.separator {
border: 0;
height: 20px;
opacity: 0.5;
background-color: transparent !important;
background-image: url("data:image/svg+xml;utf8, <svg viewBox='0 0 200 110' xmlns='http://www.w3.org/2000/svg'><path d='M -15 110 L100 10 L215 110' fill='none' stroke='%23000000' stroke-width='1' vector-effect='non-scaling-stroke'/></svg>");
background-position: bottom left;
background-size: 2% auto;
background-repeat: repeat-x;
}
h6 {
text-transform: uppercase;
text-align: center;
letter-spacing: 0.1em;
margin-top: -20px !important;
z-index: 10;
}
button {
transition: all 0.3s ease;
opacity: 0;
pointer-events: none;
position: absolute;
top: 0;
right: 0;
}
&:hover button {
opacity: 1;
pointer-events: all;
}
}
::ng-deep .popover {
min-width: 30rem;
.close {
line-height: 0.7;
}
}
@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchComponent } from './search.component';
import { SplitMergeComponent } from './split-merge.component';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
describe('SplitMergeComponent', () => {
let component: SplitMergeComponent;
let fixture: ComponentFixture<SplitMergeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SearchComponent ]
declarations: [ SplitMergeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
fixture = TestBed.createComponent(SplitMergeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -0,0 +1,212 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { SplitMergeMetadata } from 'src/app/data/split-merge-request';
import { SplitMergeService } from 'src/app/services/split-merge.service';
import { DocumentService } from 'src/app/services/rest/document.service';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { PaperlessDocument, PaperlessDocumentPart } from 'src/app/data/paperless-document';
import { DndDropEvent } from 'ngx-drag-drop';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { DocumentChooserComponent } from 'src/app/components/common/document-chooser/document-chooser.component'
import { PageChooserComponent } from 'src/app/components/common/page-chooser/page-chooser.component'
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
@Component({
selector: 'app-split-merge',
templateUrl: './split-merge.component.html',
styleUrls: ['./split-merge.component.scss']
})
export class SplitMergeComponent implements OnInit, OnDestroy {
public loading: boolean = false
public previewUrls: string[] = []
public previewNumPages: number[] = []
public previewCurrentPages: number[] = []
public delete_source_documents: boolean = false
public metadata_setting: SplitMergeMetadata = SplitMergeMetadata.COPY_FIRST
private previewDebounce$ = new Subject()
private previewSub: Subscription
public error: string
constructor(
public splitMergeService: SplitMergeService,
private documentService: DocumentService,
private list: DocumentListViewService,
private router: Router,
private modalService: NgbModal
) { }
ngOnInit() {
this.previewSub = this.previewDebounce$.pipe(
debounceTime(400)
).subscribe(() => {
if (this.splitMergeService.hasDocuments()) {
this.save(true)
} else {
this.previewUrls = []
this.error = undefined
}
})
this.previewDebounce$.next()
}
ngOnDestroy() {
this.previewDebounce$.complete()
}
get documents(): PaperlessDocument[] {
return this.splitMergeService.getDocuments()
}
getThumbUrl(documentId: number) {
return this.documentService.getThumbUrl(documentId)
}
get splitMergeMetadata() {
return SplitMergeMetadata
}
chooseDocuments() {
let modal = this.modalService.open(DocumentChooserComponent, { backdrop: 'static', size: 'xl' })
modal.componentInstance.confirmClicked.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.splitMergeService.addDocuments(this.list.selectedDocuments)
this.list.selectNone()
modal.close()
this.previewDebounce$.next()
})
}
onDragged(document: PaperlessDocument, documents: PaperlessDocument[]) {
const index = this.documents.indexOf(document)
this.documents.splice(index, 1)
}
onDrop(event: DndDropEvent, documents: PaperlessDocument[]) {
let index = event.index
if (typeof index === "undefined") {
index = this.documents.length
}
this.documents.splice(index, 0, event.data)
this.previewDebounce$.next()
}
cancel() {
this.splitMergeService.clear()
this.router.navigate([""])
}
save(preview: boolean = false) {
this.loading = true
this.previewUrls = []
this.splitMergeService.executeSplitMerge(preview, this.delete_source_documents, this.metadata_setting).subscribe(
result => {
this.loading = false
this.error = undefined
if (preview) {
this.previewUrls = result.map(r => this.splitMergeService.getPreviewUrl(r))
} else {
this.splitMergeService.clear()
this.router.navigate([""])
}
},
error => {
this.error = error
this.loading = false
}
)
}
removeDocument(d: PaperlessDocument, index: number) {
this.splitMergeService.removeDocument(d, index)
this.previewNumPages[index] = this.previewCurrentPages[index] = undefined
this.previewDebounce$.next()
}
choosePages(d: PaperlessDocument, index: number) {
let modal = this.modalService.open(PageChooserComponent, { backdrop: 'static', size: 'lg' })
modal.componentInstance.document = d
modal.componentInstance.confirmPages.subscribe((pages) => {
this.splitMergeService.setDocumentPages(d, index, pages)
modal.componentInstance.buttonsEnabled = false
modal.close()
this.previewDebounce$.next()
})
}
chooseSplit(d: PaperlessDocument, index: number) {
let modal = this.modalService.open(PageChooserComponent, { backdrop: 'static', size: 'lg' })
modal.componentInstance.document = d
const enabledPages = (d as PaperlessDocumentPart).pages
modal.componentInstance.splitting = true
modal.componentInstance.confirmPages.subscribe((pages) => {
this.splitMergeService.splitDocument(d, index, pages, enabledPages)
modal.componentInstance.buttonsEnabled = false
modal.close()
this.previewDebounce$.next()
})
}
pagesFieldChange(pageStr: string, d: PaperlessDocument, index: number) {
let pages = pageStr.split(',').map(p => {
if (p.indexOf('-') !== -1) {
const minmax = p.split('-')
let range = []
for (let i = parseInt(minmax[0]); i <= parseInt(minmax[1]); i++) {
range.push(i)
}
return range
} else if (p.length > 0) {
return parseInt(p)
} else {
return null
}
})
pages = [].concat.apply([], pages) // e.g. flat()
pages = pages.filter(page => page !== null)
this.splitMergeService.setDocumentPages(d, index, pages as number[])
this.previewDebounce$.next()
}
formatPages(pages: number[]): string {
let pageStrings = []
let rangeStart, rangeEnd
pages?.forEach(page => {
if (rangeStart == undefined) {
rangeStart = page
} else if (page - rangeStart == 1) {
rangeEnd = page
} else if (rangeEnd !== undefined && page - rangeEnd == 1) {
rangeEnd = page
} else {
pageStrings.push(rangeEnd !== undefined ? rangeStart + '-' + rangeEnd : rangeStart)
rangeStart = page
rangeEnd = undefined
}
})
if (rangeEnd !== undefined) {
pageStrings.push(rangeStart + '-' + rangeEnd)
} else if (rangeStart !== undefined) {
pageStrings.push(rangeStart)
}
return pageStrings.join(',')
}
pdfPreviewLoaded(pdf: PDFDocumentProxy, index: number) {
this.previewNumPages[index] = pdf.numPages
}
pdfPageRendered(event: any, index) { // CustomEvent is es6
if (event.pageNumber > 0 && this.previewCurrentPages[index] == undefined) this.previewCurrentPages[index] = 1
}
}
+8 -1
View File
@@ -22,6 +22,9 @@ 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: ""},
@@ -51,7 +54,11 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
{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_TITLE_CONTENT, filtervar: "title_content", datatype: "string", 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 {
+19
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,14 @@ export interface PaperlessDocument extends ObjectWithId {
archive_serial_number?: number
__search_hit__?: SearchHit
}
export interface PaperlessDocumentPart extends PaperlessDocument {
pages: number[]
}
export interface PaperlessDocumentSeparator extends PaperlessDocument {
is_separator: boolean
}
-29
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[]
}
@@ -0,0 +1,29 @@
export enum SplitMergeMetadata {
REDO = "redo",
COPY_FIRST = "copy_first"
}
export interface SplitMergePlanDocumentPart {
document: number,
pages?: string
}
export type SplitMergePlan = SplitMergePlanDocumentPart[][]
export interface SplitMergeRequest {
split_merge_plan: SplitMergePlan
delete_source: boolean
metadata: SplitMergeMetadata
preview: boolean
}
+2 -2
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({
@@ -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()
@@ -181,6 +189,10 @@ export class DocumentListViewService {
return this.activeListViewState.selected
}
get selectedDocuments(): PaperlessDocument[] {
return this.activeListViewState.documents.filter(d => this.activeListViewState.selected.has(d.id))
}
setSort(field: string, reverse: boolean) {
this.activeListViewState.sortField = field
this.activeListViewState.sortReverse = reverse
@@ -197,7 +209,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 +219,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 +333,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 +348,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)
}
}
}
@@ -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
}
}
+1 -26
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)})
+10 -5
View File
@@ -89,12 +89,17 @@ export class SettingsService {
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: "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", name: $localize`Italian`, englishName: "Italian", dateInputFormat: "dd/mm/yyyy"},
{code: "ro", name: $localize`Romanian`, englishName: "Romanian", 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"},
{code: "pl-pl", name: $localize`Polish`, englishName: "Polish", dateInputFormat: "dd.mm.yyyy"},
{code: "sv-se", name: $localize`Swedish`, englishName: "Swedish", dateInputFormat: "yyyy-mm-dd"}
]
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SplitMergeService } from './split-merge.service';
describe('SplitMergeService', () => {
let service: SplitMergeService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SplitMergeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,103 @@
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { PaperlessDocument, PaperlessDocumentPart, PaperlessDocumentSeparator } from '../data/paperless-document';
import { SplitMergeMetadata, SplitMergeRequest } from '../data/split-merge-request';
@Injectable({
providedIn: 'root'
})
export class SplitMergeService {
// this also needs to incorporate pages, if we want to support that.
private documents: PaperlessDocument[] = []
constructor(private http: HttpClient) { }
addDocument(document: PaperlessDocument, atIndex?: number) {
if (atIndex !== undefined) this.documents.splice(atIndex, 0, document)
else this.documents.push(document)
}
addDocuments(documents: PaperlessDocument[]) {
this.documents = this.documents.concat(documents)
}
removeDocument(document: PaperlessDocument, atIndex?: number) {
if (!atIndex) atIndex = this.documents.indexOf(document)
this.documents.splice(atIndex, 1)
this.sanitizeStartEndSeparators()
}
getDocuments() {
return this.documents
}
hasDocuments(): boolean {
return this.documents.length > 0
}
clear() {
this.documents = []
}
setDocumentPages(d: PaperlessDocument, index: number, pages: number[]) {
(this.documents[index] as PaperlessDocumentPart).pages = pages.length > 0 ? pages : null
}
splitDocument(d: PaperlessDocument, index: number, secondPages: number[], enabledPages?: number[]) {
const firstPages = []
for (let page = 1; page < secondPages[0]; page++) {
if (!enabledPages || (enabledPages?.length && enabledPages.indexOf(page) !== -1)) firstPages.push(page)
}
(this.documents[index] as PaperlessDocumentPart).pages = firstPages
this.documents.splice(index + 1, 0, { is_separator: true } as PaperlessDocumentSeparator)
let d2 = { ...d } as PaperlessDocumentPart
d2.pages = secondPages
this.documents.splice(index + 2, 0, d2)
}
sanitizeStartEndSeparators() {
if (this.documents.length > 0 && (this.documents[this.documents.length - 1] as PaperlessDocumentSeparator).is_separator) {
this.documents.pop()
} else if (this.documents.length > 0 && (this.documents[0] as PaperlessDocumentSeparator).is_separator) {
this.documents.shift()
}
}
executeSplitMerge(preview: boolean, delete_source: boolean, metadata: SplitMergeMetadata): Observable<string[]> {
this.sanitizeStartEndSeparators()
let currentDocument = []
let split_merge_plan = [currentDocument]
this.documents.forEach(d => {
if ((d as PaperlessDocumentSeparator).is_separator) {
currentDocument = []
split_merge_plan.push(currentDocument)
} else {
currentDocument.push({document: d.id, pages: (d as PaperlessDocumentPart).pages?.join(',')})
}
})
let request: SplitMergeRequest = {
delete_source: delete_source,
preview: preview,
metadata: metadata,
split_merge_plan: split_merge_plan
}
return this.http.post<string[]>(`${environment.apiBaseUrl}split_merge/`, request).pipe(
catchError(this.handleError)
)
}
handleError(error: HttpErrorResponse) {
console.log('API split_merge result error:', error)
return throwError(error.message)
}
getPreviewUrl(previewKey: string) {
return `${environment.apiBaseUrl}split_merge/${previewKey}/`
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ export const environment = {
apiBaseUrl: "/api/",
apiVersion: "2",
appTitle: "Paperless-ng",
version: "1.3.0",
version: "1.4.2",
webSocketHost: window.location.host,
webSocketProtocol: (window.location.protocol == "https:" ? "wss:" : "ws:")
};
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff. Show more