mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-01 18:37:42 -05:00
Compare commits
923 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
014859185c | ||
![]() |
6dae900d74 | ||
![]() |
a4187b2b70 | ||
![]() |
556e4f7430 | ||
![]() |
02c2d388ed | ||
![]() |
c0692f03a3 | ||
![]() |
a2342d98d3 | ||
![]() |
24c20e38ff | ||
![]() |
cd12c1b300 | ||
![]() |
a008dff368 | ||
![]() |
769356733a | ||
![]() |
f5be2ac4bb | ||
![]() |
2824fcb497 | ||
![]() |
2f42eab217 | ||
![]() |
338324d5b6 | ||
![]() |
0628d10076 | ||
![]() |
bc4017d54a | ||
![]() |
96dc7583bf | ||
![]() |
deb9698ff6 | ||
![]() |
654c7fd2ac | ||
![]() |
24e34e95cd | ||
![]() |
d093c004fb | ||
![]() |
ceb9426fd4 | ||
![]() |
e4f2016678 | ||
![]() |
40842689fd | ||
![]() |
59f955e08b | ||
![]() |
91e6503503 | ||
![]() |
c7c98c623d | ||
![]() |
80aa847457 | ||
![]() |
f1c981338c | ||
![]() |
d4225a459e | ||
![]() |
75ae37d90f | ||
![]() |
2159115679 | ||
![]() |
0384de9a3c | ||
![]() |
ae2e1fc381 | ||
![]() |
d45993c905 | ||
![]() |
4e6c7b01b6 | ||
![]() |
775ce56d2e | ||
![]() |
d41c466f6e | ||
![]() |
387135ffa5 | ||
![]() |
64b176103e | ||
![]() |
7f88c7d70c | ||
![]() |
1a89ad95e0 | ||
![]() |
376f991db5 | ||
![]() |
623893b528 | ||
![]() |
890f6f35b5 | ||
![]() |
ab4af350dd | ||
![]() |
95bfc4be9a | ||
![]() |
95d9c8fa2b | ||
![]() |
83dd5f51cd | ||
![]() |
5d1dddfac8 | ||
![]() |
50ba88231f | ||
![]() |
221cf12184 | ||
![]() |
681b769a71 | ||
![]() |
27517c15ca | ||
![]() |
56f566e13a | ||
![]() |
6131a40c31 | ||
![]() |
040e652e6a | ||
![]() |
caf0e1d15b | ||
![]() |
9dd8b7cd88 | ||
![]() |
8702a739b0 | ||
![]() |
8104851c08 | ||
![]() |
0e03c48beb | ||
![]() |
3fbf24a1c2 | ||
![]() |
64a966c9a5 | ||
![]() |
18a128b917 | ||
![]() |
cfd263da90 | ||
![]() |
0f4a7f0a04 | ||
![]() |
ae5dd62105 | ||
![]() |
b73ff095e6 | ||
![]() |
4503be110a | ||
![]() |
7fef27e6de | ||
![]() |
d8eabd66ea | ||
![]() |
a62e5f32b8 | ||
![]() |
24fe5bb838 | ||
![]() |
5217acc471 | ||
![]() |
f96d97a637 | ||
![]() |
de38f46a53 | ||
![]() |
8f94607409 | ||
![]() |
88ee3694f7 | ||
![]() |
584307dc54 | ||
![]() |
62fd32afbc | ||
![]() |
bfcfcde71a | ||
![]() |
6ce0945aeb | ||
![]() |
f701479d77 | ||
![]() |
4e93d92e46 | ||
![]() |
ebcdcc6a1e | ||
![]() |
b5cd713e16 | ||
![]() |
0adb8cea91 | ||
![]() |
bce79189e5 | ||
![]() |
8927e0185b | ||
![]() |
df95fad9be | ||
![]() |
e96809a08e | ||
![]() |
7ebc1351fe | ||
![]() |
ee31249d65 | ||
![]() |
a9d6e7f402 | ||
![]() |
296be9135a | ||
![]() |
f24640dec2 | ||
![]() |
64d6e6eded | ||
![]() |
587cd80055 | ||
![]() |
527b7bee3f | ||
![]() |
e1f52d706f | ||
![]() |
2e2e69f07c | ||
![]() |
6a3e4f0857 | ||
![]() |
ff3bdd7f5a | ||
![]() |
1e185d1502 | ||
![]() |
8f9aab407d | ||
![]() |
4cf0ce1b06 | ||
![]() |
8430aa75da | ||
![]() |
f5a47de1d6 | ||
![]() |
1fe322bbac | ||
![]() |
f6da608154 | ||
![]() |
22fc16f4a3 | ||
![]() |
def85a3d37 | ||
![]() |
f638079361 | ||
![]() |
e87ab91d0e | ||
![]() |
fb56602081 | ||
![]() |
2c1d9f2d7b | ||
![]() |
49385959b8 | ||
![]() |
3511c86abd | ||
![]() |
9908580409 | ||
![]() |
5bad23d7be | ||
![]() |
8cde2cd4b2 | ||
![]() |
74db555f78 | ||
![]() |
a1a415a5d1 | ||
![]() |
49053b6ad2 | ||
![]() |
fbfdd95958 | ||
![]() |
2c065da987 | ||
![]() |
6c7281ae88 | ||
![]() |
4bb0da397a | ||
![]() |
6a355b8dff | ||
![]() |
aa1fb63714 | ||
![]() |
c9ce69c1c1 | ||
![]() |
06ee41226d | ||
![]() |
bf634eee66 | ||
![]() |
e606ae5097 | ||
![]() |
ecc40d022d | ||
![]() |
262e8ee999 | ||
![]() |
141315cf42 | ||
![]() |
6fa0d00b8f | ||
![]() |
47339c84b3 | ||
![]() |
692a5081c7 | ||
![]() |
28c3f8d614 | ||
![]() |
fb5d35e43e | ||
![]() |
a4bff61e5b | ||
![]() |
2c4bbf8a00 | ||
![]() |
d2ec38d5fd | ||
![]() |
517e037724 | ||
![]() |
0051d9f76e | ||
![]() |
c0915a3dee | ||
![]() |
4363d4c9ed | ||
![]() |
d7095f7ee1 | ||
![]() |
ebc5f1a01d | ||
![]() |
d6ad41ab3a | ||
![]() |
9c2e55407f | ||
![]() |
7c70659212 | ||
![]() |
5822273c8d | ||
![]() |
001cd83ef8 | ||
![]() |
b48c290e9c | ||
![]() |
327f00e312 | ||
![]() |
b1ae8d1332 | ||
![]() |
38cb8201a1 | ||
![]() |
e3478edeeb | ||
![]() |
69f1931f4f | ||
![]() |
6eb8908a16 | ||
![]() |
88dcd889fa | ||
![]() |
9cd6235947 | ||
![]() |
bcb2853150 | ||
![]() |
ad241f12a3 | ||
![]() |
b29627c92b | ||
![]() |
8518d583d9 | ||
![]() |
81dee38e4a | ||
![]() |
fa6cc9692e | ||
![]() |
c3a92d659e | ||
![]() |
ffed65533b | ||
![]() |
5a393569f0 | ||
![]() |
6cf20a93d0 | ||
![]() |
a2b5fb8374 | ||
![]() |
f4f9200fba | ||
![]() |
52640f08d2 | ||
![]() |
bbb5783b4f | ||
![]() |
cde937a9eb | ||
![]() |
a4ee4e9b17 | ||
![]() |
34a06435cf | ||
![]() |
fad6e7284a | ||
![]() |
29f73b2dbb | ||
![]() |
968725f09f | ||
![]() |
42064df3e0 | ||
![]() |
c980a52244 | ||
![]() |
ed5c50db7d | ||
![]() |
b463428a40 | ||
![]() |
38f279fd2a | ||
![]() |
d3ab4d2f11 | ||
![]() |
1170827f3d | ||
![]() |
838631b057 | ||
![]() |
0a469cfdd1 | ||
![]() |
fc82121604 | ||
![]() |
25444034ab | ||
![]() |
eec1dbe0a0 | ||
![]() |
47e8bdb752 | ||
![]() |
75fc373b51 | ||
![]() |
8a397034fd | ||
![]() |
89476e4479 | ||
![]() |
1b61765df8 | ||
![]() |
40da245d8d | ||
![]() |
1238d8b994 | ||
![]() |
460d202eb0 | ||
![]() |
248e4e5eb9 | ||
![]() |
08299fb808 | ||
![]() |
c06f6145a1 | ||
![]() |
6101f42ae3 | ||
![]() |
9ffd9027fa | ||
![]() |
10995788d1 | ||
![]() |
98bc51a3ec | ||
![]() |
cc9f9b6e8b | ||
![]() |
7f0f48ddac | ||
![]() |
e92046a265 | ||
![]() |
fe00dffb70 | ||
![]() |
a02ddeb722 | ||
![]() |
9b3bc62132 | ||
![]() |
d7b3e7bd69 | ||
![]() |
d3fda57b6d | ||
![]() |
257ebb88f0 | ||
![]() |
9257cdac8f | ||
![]() |
19fddc8da8 | ||
![]() |
44da3fd195 | ||
![]() |
e452c161ba | ||
![]() |
c7c167a058 | ||
![]() |
6f3cdbc165 | ||
![]() |
5b4a3e4288 | ||
![]() |
e783494022 | ||
![]() |
b7ff94900f | ||
![]() |
e761cc76fa | ||
![]() |
96da61f4e4 | ||
![]() |
ca3d62f377 | ||
![]() |
0584ceb157 | ||
![]() |
633b0dd928 | ||
![]() |
cad2f77490 | ||
![]() |
a40448a350 | ||
![]() |
01df596dc7 | ||
![]() |
176b0416cd | ||
![]() |
b0449d59da | ||
![]() |
804222d68a | ||
![]() |
858bca0f7d | ||
![]() |
ac459b84c6 | ||
![]() |
716005fbd2 | ||
![]() |
086dccc177 | ||
![]() |
e13dbe4881 | ||
![]() |
6a16bdf5fd | ||
![]() |
f3b46f50bf | ||
![]() |
14a2ad2b0d | ||
![]() |
fb539865e0 | ||
![]() |
7d92caccf0 | ||
![]() |
2e71eee7b4 | ||
![]() |
507085ee7b | ||
![]() |
4690b273cc | ||
![]() |
335bdb820f | ||
![]() |
b17d9f850e | ||
![]() |
7f8ba75d90 | ||
![]() |
842b951549 | ||
![]() |
f373211281 | ||
![]() |
c5500db9ef | ||
![]() |
729d2da764 | ||
![]() |
2587d7fec8 | ||
![]() |
a6c6f64453 | ||
![]() |
b31bfeebd7 | ||
![]() |
e107d5df6f | ||
![]() |
6e84668884 | ||
![]() |
63d2dcc1f7 | ||
![]() |
0ee6426eb5 | ||
![]() |
ac2cac6edc | ||
![]() |
73682d22d6 | ||
![]() |
bef80037da | ||
![]() |
056b9638ab | ||
![]() |
be94a8e49a | ||
![]() |
7587150f96 | ||
![]() |
e97b06674c | ||
![]() |
e82700a826 | ||
![]() |
05c16e1539 | ||
![]() |
50fa69aca4 | ||
![]() |
cb3001ac3b | ||
![]() |
9bbcb9319c | ||
![]() |
32f371fcb6 | ||
![]() |
16559e83f5 | ||
![]() |
052c8c5372 | ||
![]() |
b0588321e0 | ||
![]() |
574ab7d696 | ||
![]() |
336b908e8f | ||
![]() |
b41dd2780e | ||
![]() |
7e99d7515f | ||
![]() |
c9de592832 | ||
![]() |
69ac7b9bcf | ||
![]() |
8268607a56 | ||
![]() |
c33e9245bf | ||
![]() |
4d5166d568 | ||
![]() |
fdc8060071 | ||
![]() |
e139ce77ee | ||
![]() |
ed6f2e40cf | ||
![]() |
05f57249d6 | ||
![]() |
e91befabc0 | ||
![]() |
2e641b0e30 | ||
![]() |
7e36986a26 | ||
![]() |
586db3bc5e | ||
![]() |
51fd19c315 | ||
![]() |
426ad30a52 | ||
![]() |
111ed38cce | ||
![]() |
e07128a145 | ||
![]() |
610fa075f6 | ||
![]() |
5e75d84920 | ||
![]() |
4d97a825d9 | ||
![]() |
bac739a6d9 | ||
![]() |
d935dcd350 | ||
![]() |
be2061b74d | ||
![]() |
2aa2086dfb | ||
![]() |
a89d4ee434 | ||
![]() |
cd5d762cbc | ||
![]() |
a96ab9a9a4 | ||
![]() |
f0a1aed029 | ||
![]() |
7b56ad9dad | ||
![]() |
e05f365e6a | ||
![]() |
c15e94f759 | ||
![]() |
d93ec0d5c7 | ||
![]() |
74cf5373b9 | ||
![]() |
4ddc034e9c | ||
![]() |
061dc04755 | ||
![]() |
0e4597131b | ||
![]() |
57f77c4657 | ||
![]() |
89d6e422f5 | ||
![]() |
520f92503f | ||
![]() |
4cef4adc7e | ||
![]() |
e97ff3d671 | ||
![]() |
97e96d02f2 | ||
![]() |
654ee4e62e | ||
![]() |
87fe1be1ed | ||
![]() |
4d7d3afc7d | ||
![]() |
7bca5bf40e | ||
![]() |
158e83c425 | ||
![]() |
fa0ccff5b2 | ||
![]() |
a0505aa8e9 | ||
![]() |
86677ee6b4 | ||
![]() |
eb91ac0c3c | ||
![]() |
a21fb36cd2 | ||
![]() |
28209bb474 | ||
![]() |
376efcde27 | ||
![]() |
4684d19953 | ||
![]() |
4bea504690 | ||
![]() |
90f4ccfb94 | ||
![]() |
d292f71007 | ||
![]() |
4a45a77753 | ||
![]() |
ef28ae8117 | ||
![]() |
b8b0b61e67 | ||
![]() |
41c1b711c2 | ||
![]() |
8ab251c5f7 | ||
![]() |
659c7cdbd8 | ||
![]() |
ef9d8c64fc | ||
![]() |
e6927a196f | ||
![]() |
729e5ee0e1 | ||
![]() |
a3f214b6ba | ||
![]() |
a2f0a9687d | ||
![]() |
820920839d | ||
![]() |
0d5d8f7c80 | ||
![]() |
bf198f37db | ||
![]() |
c6af2044ce | ||
![]() |
a9b331c5fd | ||
![]() |
cb88ffff79 | ||
![]() |
bcf17bfdc0 | ||
![]() |
110ef17ed8 | ||
![]() |
348d07757b | ||
![]() |
eaeda47690 | ||
![]() |
fdf330276e | ||
![]() |
dace3dc803 | ||
![]() |
70d48b39f3 | ||
![]() |
d9f5bc7681 | ||
![]() |
52aa78acab | ||
![]() |
b73ec76146 | ||
![]() |
70e49a61fa | ||
![]() |
7904a3efb7 | ||
![]() |
717a4951fd | ||
![]() |
a3b4349b1c | ||
![]() |
4b74cd5677 | ||
![]() |
6d554bace1 | ||
![]() |
c4367818b7 | ||
![]() |
ebdddc4fe4 | ||
![]() |
bac0dbb70b | ||
![]() |
0a7412424d | ||
![]() |
d5601b7ec0 | ||
![]() |
b79d88d5ef | ||
![]() |
851d9cc313 | ||
![]() |
40ef375c15 | ||
![]() |
de32addf76 | ||
![]() |
1e541b688d | ||
![]() |
c05bfb894a | ||
![]() |
279e269a66 | ||
![]() |
8490b65bcc | ||
![]() |
1234634ba3 | ||
![]() |
3d2d9cb3a2 | ||
![]() |
7740961697 | ||
![]() |
22c77e7be5 | ||
![]() |
6566e36141 | ||
![]() |
a4e5d36a02 | ||
![]() |
54a0da8151 | ||
![]() |
dc525783ee | ||
![]() |
a5ae056c9b | ||
![]() |
e0c87fc556 | ||
![]() |
fb38aacde4 | ||
![]() |
564f3b9170 | ||
![]() |
9cb14e2815 | ||
![]() |
54c023523f | ||
![]() |
97d068c3bb | ||
![]() |
f090537ef4 | ||
![]() |
3f01a9e1e2 | ||
![]() |
be7bf5288b | ||
![]() |
71d7aa3fb2 | ||
![]() |
1b8c4bb1a5 | ||
![]() |
fddda75f75 | ||
![]() |
aa7e2594e2 | ||
![]() |
5236f4e58d | ||
![]() |
916ea79e57 | ||
![]() |
b855222ee1 | ||
![]() |
fc31195fa0 | ||
![]() |
70cb27bf0a | ||
![]() |
2a744a3a83 | ||
![]() |
be56707a8a | ||
![]() |
a74404170d | ||
![]() |
2e544326e5 | ||
![]() |
d42c13a9c6 | ||
![]() |
020696fb17 | ||
![]() |
06bf3e27d3 | ||
![]() |
3b17f9d6ec | ||
![]() |
9c8b43f602 | ||
![]() |
70f052cb5c | ||
![]() |
e5c10fcd93 | ||
![]() |
9eb377703e | ||
![]() |
4c63dad309 | ||
![]() |
45e52aa985 | ||
![]() |
6066d00c5e | ||
![]() |
59a10a8127 | ||
![]() |
6dc8edea88 | ||
![]() |
ef1ee8e0c0 | ||
![]() |
1428b26703 | ||
![]() |
63b841c496 | ||
![]() |
208f4291d6 | ||
![]() |
750d08ec01 | ||
![]() |
8d665aeac4 | ||
![]() |
a0f983f05d | ||
![]() |
1df922ef16 | ||
![]() |
beed820602 | ||
![]() |
b6722fdd84 | ||
![]() |
eb47fb5501 | ||
![]() |
aacf7ba275 | ||
![]() |
400392655e | ||
![]() |
713985f259 | ||
![]() |
664280ccce | ||
![]() |
9fe6c5c3a9 | ||
![]() |
ab5098a036 | ||
![]() |
b092bb6848 | ||
![]() |
17ded12375 | ||
![]() |
118149c539 | ||
![]() |
57da323cea | ||
![]() |
ebe2ba401d | ||
![]() |
a67443c195 | ||
![]() |
2e0d36c4d9 | ||
![]() |
db4b621631 | ||
![]() |
a91958bfe1 | ||
![]() |
5395208b00 | ||
![]() |
d909010f08 | ||
![]() |
5c7b65163e | ||
![]() |
e4e4efcba7 | ||
![]() |
9f18d0ad45 | ||
![]() |
2de3894d67 | ||
![]() |
03c6a4e18e | ||
![]() |
f9ab8d3b35 | ||
![]() |
14f87f5aee | ||
![]() |
bb569b4e78 | ||
![]() |
ef63ec40d9 | ||
![]() |
761a6a4264 | ||
![]() |
8139ecfd39 | ||
![]() |
fb09f67899 | ||
![]() |
f075384b44 | ||
![]() |
b8e8bf3dd4 | ||
![]() |
d690b34ee0 | ||
![]() |
e24b40de29 | ||
![]() |
bf3ffc29a9 | ||
![]() |
6581cff8dc | ||
![]() |
5c3ae44021 | ||
![]() |
2b341afcae | ||
![]() |
7f4cfc0b76 | ||
![]() |
b2327d6fde | ||
![]() |
f964dd5935 | ||
![]() |
4ecd5ada06 | ||
![]() |
f770f0444a | ||
![]() |
39d1c051cf | ||
![]() |
5622e13802 | ||
![]() |
d6285cc851 | ||
![]() |
777d8b4609 | ||
![]() |
edb9264d78 | ||
![]() |
6b708c5ed3 | ||
![]() |
1dd2386fa5 | ||
![]() |
e6164eb1ab | ||
![]() |
bd01b821ec | ||
![]() |
b44ca770a5 | ||
![]() |
9093ac5901 | ||
![]() |
755da317ea | ||
![]() |
6834c70bae | ||
![]() |
1297c0911c | ||
![]() |
ebcf4e2d81 | ||
![]() |
bd3a2306d6 | ||
![]() |
27666da4e9 | ||
![]() |
70f7b614e6 | ||
![]() |
9bf4ce25b2 | ||
![]() |
d51848cdc0 | ||
![]() |
7d86ee32af | ||
![]() |
70c02a1c82 | ||
![]() |
39637fc4aa | ||
![]() |
1de7a490b4 | ||
![]() |
6d4aa76405 | ||
![]() |
27ae4f6b1e | ||
![]() |
d6e733c56f | ||
![]() |
7497e0f6b9 | ||
![]() |
aa6e96e54d | ||
![]() |
3d173a13ab | ||
![]() |
bdf2e29843 | ||
![]() |
7beb8a0929 | ||
![]() |
8af0259671 | ||
![]() |
67953c98a9 | ||
![]() |
527c533958 | ||
![]() |
544ca8d008 | ||
![]() |
bd02c78966 | ||
![]() |
9e311241b3 | ||
![]() |
e228e18f04 | ||
![]() |
e2bea3aee3 | ||
![]() |
ba9b5c50d2 | ||
![]() |
61834581d5 | ||
![]() |
08beaf81d5 | ||
![]() |
1276419ec6 | ||
![]() |
ef9631ae24 | ||
![]() |
86079a936e | ||
![]() |
c6acf2f7f6 | ||
![]() |
28b7c3c208 | ||
![]() |
75c8cd9967 | ||
![]() |
4fb5dce5e7 | ||
![]() |
b8e7506de4 | ||
![]() |
0560ac2a95 | ||
![]() |
131ebf0480 | ||
![]() |
80420a99f5 | ||
![]() |
6a70369a77 | ||
![]() |
fb83069975 | ||
![]() |
f61ecadf76 | ||
![]() |
f040c4e593 | ||
![]() |
c2a47ca4b1 | ||
![]() |
a283b815ef | ||
![]() |
3dd4583ea8 | ||
![]() |
bf79b252ad | ||
![]() |
a2ad62b310 | ||
![]() |
4b9a8f3409 | ||
![]() |
802bd7fb0d | ||
![]() |
320298e3ff | ||
![]() |
5e5059b2e7 | ||
![]() |
d6d7528668 | ||
![]() |
3f8c74c4af | ||
![]() |
0a1b810da7 | ||
![]() |
25fb9fb185 | ||
![]() |
c547128238 | ||
![]() |
99b5e25731 | ||
![]() |
20e724da46 | ||
![]() |
03838421bc | ||
![]() |
b9cf517cd1 | ||
![]() |
f7f78d80b7 | ||
![]() |
e63b820c07 | ||
![]() |
2ac6a02e31 | ||
![]() |
6e79b771ec | ||
![]() |
37caf6a64a | ||
![]() |
198354d07d | ||
![]() |
eec9716ffa | ||
![]() |
64d0c7fae6 | ||
![]() |
02e81f7ab5 | ||
![]() |
85cf931d0a | ||
![]() |
c54d26ed19 | ||
![]() |
cffe9fa354 | ||
![]() |
46b0776714 | ||
![]() |
929b25a969 | ||
![]() |
3c2fac3d28 | ||
![]() |
9a165e87fd | ||
![]() |
95c4e77ae4 | ||
![]() |
ba3696566f | ||
![]() |
6907b91420 | ||
![]() |
227934a7f0 | ||
![]() |
089a8c0498 | ||
![]() |
92fa978735 | ||
![]() |
8135de49f0 | ||
![]() |
ccfa14b9b7 | ||
![]() |
6589369e1b | ||
![]() |
9c2c74ad2b | ||
![]() |
b7c118afa3 | ||
![]() |
e9b5f8d9f8 | ||
![]() |
e48294a74b | ||
![]() |
50c5a23de8 | ||
![]() |
7d676a75a8 | ||
![]() |
544e8db722 | ||
![]() |
ba066af664 | ||
![]() |
c4286d0a48 | ||
![]() |
1c535e6ff1 | ||
![]() |
0b9ea5c60f | ||
![]() |
0fa3c3188d | ||
![]() |
ebcb2cf694 | ||
![]() |
8dc15352ef | ||
![]() |
403de42031 | ||
![]() |
b08a5e62d8 | ||
![]() |
1f62b6d0d4 | ||
![]() |
c751fe7520 | ||
![]() |
978bb3c339 | ||
![]() |
3e389a3140 | ||
![]() |
0dbd598116 | ||
![]() |
46ecdefde8 | ||
![]() |
e8ea373eb2 | ||
![]() |
01d10da061 | ||
![]() |
0aad31b4bc | ||
![]() |
2f599ca1d3 | ||
![]() |
162626209c | ||
![]() |
cd8f99d2c3 | ||
![]() |
6968e228e1 | ||
![]() |
b7d310ef90 | ||
![]() |
1c3b85249c | ||
![]() |
bf3b110804 | ||
![]() |
7d1f931234 | ||
![]() |
147a8774c6 | ||
![]() |
406f8daa12 | ||
![]() |
0a4a06b991 | ||
![]() |
41e842a9e6 | ||
![]() |
1fb3316436 | ||
![]() |
2f26d07480 | ||
![]() |
360fc081bb | ||
![]() |
4cae338479 | ||
![]() |
9ace199422 | ||
![]() |
f42e5bf1e5 | ||
![]() |
5863060585 | ||
![]() |
e466158ce2 | ||
![]() |
38156123d4 | ||
![]() |
ade951a600 | ||
![]() |
48b59c8e24 | ||
![]() |
b3a13cec17 | ||
![]() |
ae3e2a7063 | ||
![]() |
48d83e166b | ||
![]() |
29e9d7d793 | ||
![]() |
dcbc0ea2e5 | ||
![]() |
c527b274b6 | ||
![]() |
5892941d8a | ||
![]() |
b653e44f65 | ||
![]() |
e75534c0f2 | ||
![]() |
0e93f7eba5 | ||
![]() |
7817315f8b | ||
![]() |
ba18258750 | ||
![]() |
23381f7d17 | ||
![]() |
665863e395 | ||
![]() |
d9e3895f34 | ||
![]() |
f06e2c1089 | ||
![]() |
dd6aa2f775 | ||
![]() |
c6b9e2b544 | ||
![]() |
240d5b9da2 | ||
![]() |
5b344963b9 | ||
![]() |
b10e7abbe8 | ||
![]() |
01c2fe508e | ||
![]() |
ee31fdc650 | ||
![]() |
fa7b90a584 | ||
![]() |
37c2051e01 | ||
![]() |
3561935e1b | ||
![]() |
7669679fb1 | ||
![]() |
2e44c18cdb | ||
![]() |
86da578774 | ||
![]() |
46f778111c | ||
![]() |
7dfcc7f47b | ||
![]() |
39b35c090b | ||
![]() |
ee4e026ba2 | ||
![]() |
de6ba3489a | ||
![]() |
6381093386 | ||
![]() |
fd6bfd02ce | ||
![]() |
1da652ba4d | ||
![]() |
9a4190bedf | ||
![]() |
561db8607a | ||
![]() |
7f9a0204b5 | ||
![]() |
32224f187d | ||
![]() |
d91fa99e77 | ||
![]() |
3f94fc2618 | ||
![]() |
e79c45c98d | ||
![]() |
400da7bbc5 | ||
![]() |
bb814da95b | ||
![]() |
fad3df1e39 | ||
![]() |
1b1b57eb6a | ||
![]() |
57a5a4147b | ||
![]() |
1041504cb1 | ||
![]() |
26b40e06f8 | ||
![]() |
df8235de13 | ||
![]() |
12e45624db | ||
![]() |
37237dfcf6 | ||
![]() |
da3695e3a4 | ||
![]() |
24c53e78a7 | ||
![]() |
275bd96ba8 | ||
![]() |
01d448ecde | ||
![]() |
3cdd38cb70 | ||
![]() |
f184e6b162 | ||
![]() |
55a6dca373 | ||
![]() |
5517c049b8 | ||
![]() |
86f83f2bc4 | ||
![]() |
557abbc17e | ||
![]() |
e0293db16d | ||
![]() |
fbb2da42dc | ||
![]() |
2d841e7167 | ||
![]() |
f214fe1b3e | ||
![]() |
e5fe515b69 | ||
![]() |
dfb88ebf83 | ||
![]() |
789abb3bbb | ||
![]() |
273c474e3f | ||
![]() |
b262ec4b32 | ||
![]() |
c05de3d57f | ||
![]() |
55c4c690ef | ||
![]() |
e10a2391c4 | ||
![]() |
9b244d0265 | ||
![]() |
cfc1ca45fc | ||
![]() |
1c4e3f682e | ||
![]() |
75b22e8684 | ||
![]() |
ca2cb694d0 | ||
![]() |
cea34211b6 | ||
![]() |
d252d040bf | ||
![]() |
43c88ae0f0 | ||
![]() |
93be4e98d5 | ||
![]() |
659cd3e9d5 | ||
![]() |
2c3eaadbce | ||
![]() |
1947d67e69 | ||
![]() |
35dcc54dc8 | ||
![]() |
48796e6961 | ||
![]() |
164418880a | ||
![]() |
fbca412d30 | ||
![]() |
70347bb8f3 | ||
![]() |
eaf11ea134 | ||
![]() |
a59f8cd1b1 | ||
![]() |
e9affbc1cf | ||
![]() |
aa8789ae31 | ||
![]() |
5c310c51d4 | ||
![]() |
cf3fa50b55 | ||
![]() |
7f933d373f | ||
![]() |
ece94379d8 | ||
![]() |
aa714bded3 | ||
![]() |
ecfae9dadd | ||
![]() |
69c04a209a | ||
![]() |
e4ec52ed29 | ||
![]() |
50aedc1094 | ||
![]() |
062f8e5a73 | ||
![]() |
c813c02025 | ||
![]() |
28f45d8f15 | ||
![]() |
8e339789fa | ||
![]() |
dec17a3b9b | ||
![]() |
6b60501dc7 | ||
![]() |
0e78f32009 | ||
![]() |
b13ec571f8 | ||
![]() |
9f5e6d1969 | ||
![]() |
b2e0a8c884 | ||
![]() |
e47b105185 | ||
![]() |
8bd82f5c69 | ||
![]() |
e528a587cc | ||
![]() |
1a526ac31e | ||
![]() |
22e56f09ba | ||
![]() |
af0eafbb3b | ||
![]() |
8e57d4e791 | ||
![]() |
b2a9cf4709 | ||
![]() |
677cfb7a1e | ||
![]() |
fb9d750684 | ||
![]() |
b8469946a8 | ||
![]() |
beffde1051 | ||
![]() |
71c58c4b05 | ||
![]() |
02e67d25b4 | ||
![]() |
d208ab1e12 | ||
![]() |
55075619c1 | ||
![]() |
7dce57b9f7 | ||
![]() |
a0c74025e3 | ||
![]() |
31bea6a361 | ||
![]() |
5894060dc5 | ||
![]() |
b787983e42 | ||
![]() |
7e0aa7136a | ||
![]() |
56204933b0 | ||
![]() |
49be87fe37 | ||
![]() |
03f071fd27 | ||
![]() |
34c42c4339 | ||
![]() |
b45bd66573 | ||
![]() |
3b2bc292d8 | ||
![]() |
30185d560c | ||
![]() |
6e614eda5e | ||
![]() |
164755c755 | ||
![]() |
6d39dfeb3b | ||
![]() |
999b36473c | ||
![]() |
8b57967836 | ||
![]() |
ff71b04848 | ||
![]() |
67d03c11b9 | ||
![]() |
533be7e96e | ||
![]() |
4ed56e4603 | ||
![]() |
45848f5e34 | ||
![]() |
cf619d9d31 | ||
![]() |
de87efc291 | ||
![]() |
889fe5890d | ||
![]() |
958acd8a36 | ||
![]() |
381a503947 | ||
![]() |
b7126030d1 | ||
![]() |
13d934dc6e | ||
![]() |
926e746005 | ||
![]() |
94c07839a4 | ||
![]() |
277e668e07 | ||
![]() |
10440ec820 | ||
![]() |
02c1d496d6 | ||
![]() |
98ab79ad5a | ||
![]() |
e1fef59e82 | ||
![]() |
32201dd034 | ||
![]() |
670b6d3629 | ||
![]() |
54d90a4c4b | ||
![]() |
9bfc92cf79 | ||
![]() |
a12ec00827 | ||
![]() |
8cc0336338 | ||
![]() |
bad7caa8b9 | ||
![]() |
251fc582e9 | ||
![]() |
245af65841 | ||
![]() |
ee7492cf52 | ||
![]() |
8e5c2a2b14 | ||
![]() |
1343e4c99f | ||
![]() |
89cb1211e7 | ||
![]() |
04bb7d4893 | ||
![]() |
1fafb9ace6 | ||
![]() |
ae51619243 | ||
![]() |
7ac101d84e | ||
![]() |
2c18f6268b | ||
![]() |
7d212f6e80 | ||
![]() |
3d8cd0f0d6 | ||
![]() |
3f719a21e0 | ||
![]() |
7a75d3f41b | ||
![]() |
1ddad84985 | ||
![]() |
bcdbc975d6 | ||
![]() |
a61ea3555a | ||
![]() |
2de546fd52 | ||
![]() |
d6894d3c64 | ||
![]() |
bb1725c7dd | ||
![]() |
6f684f8070 | ||
![]() |
7712230300 | ||
![]() |
7e4c5af158 | ||
![]() |
2dc3019083 | ||
![]() |
b5a85caa72 | ||
![]() |
bb9b438aa6 | ||
![]() |
3089b049cf | ||
![]() |
5bea5e75c0 | ||
![]() |
7906d8fef1 | ||
![]() |
30853e963e | ||
![]() |
1379c039b8 | ||
![]() |
37c21e518d | ||
![]() |
e215e11417 | ||
![]() |
2374506a20 | ||
![]() |
1c4d19198f | ||
![]() |
e77be6a2ed | ||
![]() |
1fdf1ef337 | ||
![]() |
f5cc5fbaa3 | ||
![]() |
a0631413d6 | ||
![]() |
8ce4434ba9 | ||
![]() |
f6a50ee7c6 | ||
![]() |
beff45a835 | ||
![]() |
dfa1f29809 | ||
![]() |
2a57f9d6e5 | ||
![]() |
02871e1e22 | ||
![]() |
9cd40e96f4 | ||
![]() |
bf9051e44d | ||
![]() |
ebb39b13f0 | ||
![]() |
4f14e0f425 | ||
![]() |
e7cb358536 | ||
![]() |
a85792e327 | ||
![]() |
f5df910894 | ||
![]() |
80b47fa287 | ||
![]() |
1aa76f8483 | ||
![]() |
de5d360d52 | ||
![]() |
f0ad92e542 | ||
![]() |
0b7ffa31d1 | ||
![]() |
b452816a29 | ||
![]() |
d1d09ac6ac | ||
![]() |
66240188c7 | ||
![]() |
d1f285113d | ||
![]() |
56dfc71bb9 | ||
![]() |
a8f27f79dd | ||
![]() |
2c702eb568 | ||
![]() |
63a58ccc38 | ||
![]() |
4b0027797a | ||
![]() |
66d6d29c23 | ||
![]() |
fbb3a069cd | ||
![]() |
a37796d0cf | ||
![]() |
0b4c860354 | ||
![]() |
a4a08aa667 | ||
![]() |
c24bfd4d2b | ||
![]() |
ed480f62e3 | ||
![]() |
66aa7319ab | ||
![]() |
4146955f4a | ||
![]() |
57504b7ee6 | ||
![]() |
364df5c050 | ||
![]() |
f83185bfe4 | ||
![]() |
db02d68a5a | ||
![]() |
9a18954686 | ||
![]() |
edcb62476b | ||
![]() |
3a82b7806a | ||
![]() |
51b1528fee | ||
![]() |
defa80d05a | ||
![]() |
abd54eeb3a | ||
![]() |
2f7bb01f34 | ||
![]() |
25e1177198 | ||
![]() |
ab8a1cfded | ||
![]() |
fa5121082d | ||
![]() |
f0d86130ec | ||
![]() |
ed236460b5 | ||
![]() |
a4f7c5ddcb | ||
![]() |
4fbb814e5b | ||
![]() |
0f635d1bb2 | ||
![]() |
0d48aea308 | ||
![]() |
06a3fff2bc | ||
![]() |
c28f19c9cf | ||
![]() |
da87542a52 | ||
![]() |
23ba3be68f | ||
![]() |
f3fd0fcf72 | ||
![]() |
72706a335d | ||
![]() |
5369e0be03 | ||
![]() |
8699b6931c | ||
![]() |
35124023f0 | ||
![]() |
fd4c9a1758 | ||
![]() |
32186e0de1 |
@@ -1,3 +1,4 @@
|
||||
**/__pycache__
|
||||
/src-ui/.vscode
|
||||
/src-ui/node_modules
|
||||
/src-ui/dist
|
||||
@@ -10,3 +11,9 @@
|
||||
.pytest_cache
|
||||
/dist
|
||||
/scripts
|
||||
/resources
|
||||
**/tests
|
||||
**/*.spec.ts
|
||||
**/htmlcov
|
||||
/src/.pytest_cache
|
||||
.idea
|
||||
|
59
.github/workflows/ansible.yml
vendored
Normal file
59
.github/workflows/ansible.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: Ansible Role
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
# https://molecule.readthedocs.io/en/latest/ci.html#github-actions
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
# https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context
|
||||
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(github.ref, 'refs/heads/'))
|
||||
steps:
|
||||
- name: Check out the codebase
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: "${{ github.repository }}"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
- name: Set up Docker
|
||||
uses: docker-practice/actions-setup-docker@master
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install molecule[ansible,docker]
|
||||
ansible --version
|
||||
docker --version
|
||||
molecule --version
|
||||
python --version
|
||||
- name: Test with molecule
|
||||
run: |
|
||||
cd ansible
|
||||
molecule test
|
||||
working-directory: "${{ github.repository }}"
|
||||
# # https://galaxy.ansible.com/docs/contributing/importing.html
|
||||
# release:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs:
|
||||
# - test
|
||||
# # https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context
|
||||
# if: contains(github.ref, 'refs/tags/')
|
||||
# steps:
|
||||
# - name: Check out the codebase
|
||||
# uses: actions/checkout@v2
|
||||
# with:
|
||||
# path: "${{ github.repository }}"
|
||||
# - name: Set up Python
|
||||
# uses: actions/setup-python@v2
|
||||
# - name: Install dependencies
|
||||
# run: |
|
||||
# python3 -m pip install --upgrade ansible-base
|
||||
# ansible --version
|
||||
# python --version
|
||||
# - name: Trigger a new import on Galaxy
|
||||
# # TODO Check if source if pulled from cwd or imported from github
|
||||
# # https://github.com/ansible/ansible/blob/devel/lib/ansible/cli/galaxy.py
|
||||
# run: |
|
||||
# cd ansible
|
||||
# ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} $(echo ${{ github.repository }} | cut -d/ -f1) $(echo ${{ github.repository }} | cut -d/ -f2)
|
||||
# working-directory: "${{ github.repository }}"
|
288
.github/workflows/ci.yml
vendored
Normal file
288
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
name: ci
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
-
|
||||
name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
-
|
||||
name: Persistent Github pip cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip3.8}
|
||||
-
|
||||
name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends libpoppler-cpp-dev
|
||||
pip install --upgrade pipenv
|
||||
pipenv install --system --dev --ignore-pipfile
|
||||
-
|
||||
name: Make documentation
|
||||
run: |
|
||||
cd docs/
|
||||
make html
|
||||
-
|
||||
name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: documentation
|
||||
path: docs/_build/html/
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.6', '3.7', '3.8']
|
||||
fail-fast: false
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
-
|
||||
name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
-
|
||||
name: Persistent Github pip cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip${{ matrix.python-version }}
|
||||
-
|
||||
name: Prepare tests
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends libpoppler-cpp-dev unpaper tesseract-ocr imagemagick ghostscript optipng
|
||||
pip install --upgrade pipenv
|
||||
pipenv install --system --dev --ignore-pipfile
|
||||
-
|
||||
name: Tests
|
||||
run: |
|
||||
cd src/
|
||||
pytest
|
||||
-
|
||||
name: Codestyle
|
||||
run: |
|
||||
cd src/
|
||||
pycodestyle
|
||||
-
|
||||
name: Publish coverage results
|
||||
if: matrix.python-version == '3.8'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# https://github.com/coveralls-clients/coveralls-python/issues/251
|
||||
run: |
|
||||
cd src/
|
||||
coveralls --service=github
|
||||
|
||||
frontend:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '15'
|
||||
-
|
||||
name: Build frontend
|
||||
run: ./compile-frontend.sh
|
||||
-
|
||||
name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
|
||||
build-release:
|
||||
needs: [frontend, documentation, tests]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
-
|
||||
name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends libpoppler-cpp-dev gettext liblept5
|
||||
pip3 install -r requirements.txt
|
||||
-
|
||||
name: Download frontend artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
-
|
||||
name: Download documentation artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: documentation
|
||||
path: docs/_build/html/
|
||||
-
|
||||
name: Move files
|
||||
run: |
|
||||
mkdir dist
|
||||
mkdir dist/paperless-ng
|
||||
mkdir dist/paperless-ng/scripts
|
||||
cp .dockerignore .env Dockerfile Pipfile Pipfile.lock LICENSE README.md requirements.txt dist/paperless-ng/
|
||||
cp paperless.conf.example dist/paperless-ng/paperless.conf
|
||||
cp docker/ dist/paperless-ng/docker -r
|
||||
cp scripts/*.service scripts/*.sh dist/paperless-ng/scripts/
|
||||
cp src/ dist/paperless-ng/src -r
|
||||
cp docs/_build/html/ dist/paperless-ng/docs -r
|
||||
-
|
||||
name: Compile messages
|
||||
run: |
|
||||
cd dist/paperless-ng/src
|
||||
python3 manage.py compilemessages
|
||||
-
|
||||
name: Collect static files
|
||||
run: |
|
||||
cd dist/paperless-ng/src
|
||||
python3 manage.py collectstatic --no-input
|
||||
-
|
||||
name: Make release package
|
||||
run: |
|
||||
cd dist
|
||||
find . -name __pycache__ | xargs rm -r
|
||||
tar -cJf paperless-ng.tar.xz paperless-ng/
|
||||
-
|
||||
name: Upload release artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release
|
||||
path: dist/paperless-ng.tar.xz
|
||||
|
||||
publish-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-release
|
||||
if: contains(github.ref, 'refs/tags/ng-')
|
||||
steps:
|
||||
-
|
||||
name: Download release artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: release
|
||||
path: ./
|
||||
-
|
||||
name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
echo ::set-output name=version::${GITHUB_REF#refs/tags/ng-}
|
||||
-
|
||||
name: Create release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ng-${{ steps.get_version.outputs.version }}
|
||||
release_name: Paperless-ng ${{ steps.get_version.outputs.version }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
body: |
|
||||
For a complete list of changes, see the changelog at https://paperless-ng.readthedocs.io/en/latest/changelog.html.
|
||||
-
|
||||
name: Upload release archive
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ./paperless-ng.tar.xz
|
||||
asset_name: paperless-ng-${{ steps.get_version.outputs.version }}.tar.xz
|
||||
asset_content_type: application/x-xz
|
||||
|
||||
# build and push image to docker hub.
|
||||
build-docker-image:
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/ng-'))
|
||||
runs-on: ubuntu-latest
|
||||
needs: [frontend, tests]
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
VERSION=edge
|
||||
if [[ $GITHUB_REF == refs/tags/ng-* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/ng-}
|
||||
elif [[ $GITHUB_REF == refs/heads/master ]]; then
|
||||
VERSION=latest
|
||||
elif [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/heads/}
|
||||
fi
|
||||
echo ::set-output name=version::${VERSION}
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Download frontend artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
push: true
|
||||
tags: jonaswinkler/paperless-ng:${{ steps.prepare.outputs.version }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
-
|
||||
name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect jonaswinkler/paperless-ng:${{ steps.prepare.outputs.version }}
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -65,8 +65,8 @@ target/
|
||||
.virtualenv
|
||||
virtualenv
|
||||
/venv
|
||||
docker-compose.env
|
||||
docker-compose.yml
|
||||
/docker-compose.env
|
||||
/docker-compose.yml
|
||||
|
||||
# Used for development
|
||||
scripts/import-for-development
|
||||
@@ -85,3 +85,4 @@ scripts/nuke
|
||||
|
||||
# this is where the compiled frontend is moved to.
|
||||
/src/documents/static/frontend/
|
||||
/docs/.vscode/settings.json
|
||||
|
16
.readthedocs.yml
Normal file
16
.readthedocs.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
51
.travis.yml
51
.travis.yml
@@ -1,51 +0,0 @@
|
||||
language: python
|
||||
|
||||
dist: focal
|
||||
os: linux
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: "Paperless on Python 3.6"
|
||||
python: "3.6"
|
||||
|
||||
- name: "Paperless on Python 3.7"
|
||||
python: "3.7"
|
||||
|
||||
- name: "Paperless on Python 3.8"
|
||||
python: "3.8"
|
||||
|
||||
- name: "Documentation"
|
||||
script:
|
||||
- cd docs/
|
||||
- make html
|
||||
after_success: true
|
||||
|
||||
- name: "Front end"
|
||||
language: node_js
|
||||
node_js:
|
||||
- 15
|
||||
before_install: true
|
||||
install:
|
||||
- cd src-ui/
|
||||
- npm install -g @angular/cli
|
||||
- npm install
|
||||
script:
|
||||
- ng build --prod
|
||||
after_success: true
|
||||
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libpoppler-cpp-dev unpaper tesseract-ocr imagemagick ghostscript optipng
|
||||
|
||||
install:
|
||||
- pip install --upgrade pipenv
|
||||
- pipenv install --system --dev
|
||||
|
||||
script:
|
||||
- cd src/
|
||||
- pipenv run pytest --cov
|
||||
- pipenv run pycodestyle
|
||||
|
||||
after_success:
|
||||
- pipenv run coveralls
|
@@ -1,45 +1,79 @@
|
||||
FROM ubuntu:20.04 AS jbig2enc
|
||||
|
||||
WORKDIR /usr/src/jbig2enc
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends build-essential automake libtool libleptonica-dev zlib1g-dev git ca-certificates
|
||||
|
||||
RUN git clone https://github.com/agl/jbig2enc .
|
||||
RUN ./autogen.sh
|
||||
RUN ./configure && make
|
||||
|
||||
FROM python:3.7-slim
|
||||
|
||||
WORKDIR /usr/src/paperless/
|
||||
|
||||
COPY requirements.txt ./
|
||||
|
||||
#Dependencies
|
||||
# Binary dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
build-essential \
|
||||
# Basic dependencies
|
||||
curl \
|
||||
ghostscript \
|
||||
file \
|
||||
# fonts for text file thumbnail generation
|
||||
fonts-liberation \
|
||||
# for making translations further down
|
||||
gettext \
|
||||
gnupg \
|
||||
icc-profiles-free \
|
||||
imagemagick \
|
||||
# for Numpy
|
||||
libatlas-base-dev \
|
||||
liblept5 \
|
||||
libmagic-dev \
|
||||
libpoppler-cpp-dev \
|
||||
libpq-dev \
|
||||
libqpdf-dev \
|
||||
libxml2 \
|
||||
libxslt1-dev \
|
||||
mime-support \
|
||||
# thumbnail size reduction
|
||||
optipng \
|
||||
sudo \
|
||||
tzdata \
|
||||
# OCRmyPDF dependencies
|
||||
ghostscript \
|
||||
icc-profiles-free \
|
||||
liblept5 \
|
||||
libxml2 \
|
||||
pngquant \
|
||||
qpdf \
|
||||
sudo \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
tesseract-ocr-deu \
|
||||
tesseract-ocr-fra \
|
||||
tesseract-ocr-ita \
|
||||
tesseract-ocr-spa \
|
||||
tzdata \
|
||||
unpaper \
|
||||
zlib1g \
|
||||
&& pip3 install --upgrade supervisor setuptools \
|
||||
&& pip install --no-cache-dir -r requirements.txt \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# This pulls in updated dependencies from bullseye to fix some issues with file type detection.
|
||||
# TODO: Remove this once bullseye releases.
|
||||
RUN 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
|
||||
|
||||
# Python dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
build-essential \
|
||||
libpoppler-cpp-dev \
|
||||
libpq-dev \
|
||||
libqpdf-dev \
|
||||
&& python3 -m pip install --upgrade --no-cache-dir supervisor \
|
||||
&& python3 -m pip install --no-cache-dir -r requirements.txt \
|
||||
&& apt-get -y purge build-essential libqpdf-dev \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir /var/log/supervisord /var/run/supervisord
|
||||
|
||||
|
||||
# copy scripts
|
||||
# this fixes issues with imagemagick and PDF
|
||||
COPY docker/imagemagick-policy.xml /etc/ImageMagick-6/policy.xml
|
||||
@@ -47,6 +81,12 @@ COPY docker/gunicorn.conf.py ./
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/docker-entrypoint.sh /sbin/docker-entrypoint.sh
|
||||
|
||||
# copy jbig2enc
|
||||
COPY --from=jbig2enc /usr/src/jbig2enc/src/.libs/libjbig2enc* /usr/local/lib/
|
||||
COPY --from=jbig2enc /usr/src/jbig2enc/src/jbig2 /usr/local/bin/
|
||||
COPY --from=jbig2enc /usr/src/jbig2enc/src/*.h /usr/local/include/
|
||||
|
||||
|
||||
# copy app
|
||||
COPY src/ ./src/
|
||||
|
||||
@@ -60,8 +100,11 @@ WORKDIR /usr/src/paperless/src/
|
||||
|
||||
RUN sudo -HEu paperless python3 manage.py collectstatic --clear --no-input
|
||||
|
||||
RUN sudo -HEu 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"]
|
||||
EXPOSE 8000
|
||||
CMD ["/usr/local/bin/supervisord", "-c", "/etc/supervisord.conf"]
|
||||
|
||||
LABEL maintainer="Jonas Winkler <dev@jpwinkler.de>"
|
15
Pipfile
15
Pipfile
@@ -8,9 +8,6 @@ url = "https://www.piwheels.org/simple"
|
||||
verify_ssl = true
|
||||
name = "piwheels"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
|
||||
[packages]
|
||||
dateparser = "~=0.7.6"
|
||||
django = "~=3.1.3"
|
||||
@@ -26,8 +23,9 @@ imap-tools = "*"
|
||||
langdetect = "*"
|
||||
pdftotext = "*"
|
||||
pathvalidate = "*"
|
||||
pillow = "*"
|
||||
pikepdf = "*"
|
||||
# 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.2.5"
|
||||
python-gnupg = "*"
|
||||
python-dotenv = "*"
|
||||
python-dateutil = "*"
|
||||
@@ -35,13 +33,14 @@ python-Levenshtein = "*"
|
||||
python-magic = "*"
|
||||
psycopg2-binary = "*"
|
||||
redis = "*"
|
||||
scikit-learn="~=0.23.2"
|
||||
scikit-learn="~=0.24.0"
|
||||
whitenoise = "~=5.2.0"
|
||||
watchdog = "*"
|
||||
whoosh="~=2.7.4"
|
||||
inotifyrecursive = "~=0.3.4"
|
||||
ocrmypdf = "*"
|
||||
ocrmypdf = "~=11.4.5"
|
||||
tqdm = "*"
|
||||
tika = "*"
|
||||
|
||||
[dev-packages]
|
||||
coveralls = "*"
|
||||
@@ -53,6 +52,6 @@ pytest-django = "*"
|
||||
pytest-env = "*"
|
||||
pytest-sugar = "*"
|
||||
pytest-xdist = "*"
|
||||
sphinx = "~=3.3"
|
||||
sphinx = "~=3.4.2"
|
||||
sphinx_rtd_theme = "*"
|
||||
tox = "*"
|
||||
|
715
Pipfile.lock
generated
715
Pipfile.lock
generated
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "3d576f289958226a7583e4c471c7f8c11bff6933bf093185f623cfb381a92412"
|
||||
"sha256": "3c85a487240f18b3feb44f8899395696cb79630f320f0df9ef5ee37b914c89f2"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
@@ -44,6 +42,13 @@
|
||||
],
|
||||
"version": "==1.17.12"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
],
|
||||
"version": "==2020.12.5"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e",
|
||||
@@ -89,50 +94,40 @@
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '3.1'",
|
||||
"version": "==3.0.4"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"coloredlogs": {
|
||||
"hashes": [
|
||||
"sha256:346f58aad6afd48444c2468618623638dadab76e4e70d5e10822676f2d32226a",
|
||||
"sha256:a1fab193d2053aa6c0a97608c4342d031f1f93a3d1218432c59322441d31a505",
|
||||
"sha256:b0c2124367d4f72bd739f48e1f61491b4baf145d6bda33b606b4a53cb3f96a97"
|
||||
"sha256:5e78691e2673a8e294499e1832bb13efcfb44a86b92e18109fa18951093218ab",
|
||||
"sha256:b7f630a8297a66984b6bae0f6a1b0e0afb9f2f6838ea3bfa58f50d3d13e133d6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==14.0"
|
||||
"version": "==15.0"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538",
|
||||
"sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f",
|
||||
"sha256:257dab4f368fae15f378ea9a4d2799bf3696668062de0e9fa0ebb7a738a6917d",
|
||||
"sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77",
|
||||
"sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b",
|
||||
"sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33",
|
||||
"sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e",
|
||||
"sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb",
|
||||
"sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e",
|
||||
"sha256:59f7d4cfea9ef12eb9b14b83d79b432162a0a24a91ddc15c2c9bf76a68d96f2b",
|
||||
"sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7",
|
||||
"sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297",
|
||||
"sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d",
|
||||
"sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7",
|
||||
"sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b",
|
||||
"sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7",
|
||||
"sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4",
|
||||
"sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8",
|
||||
"sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b",
|
||||
"sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851",
|
||||
"sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13",
|
||||
"sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b",
|
||||
"sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3",
|
||||
"sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"
|
||||
"sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d",
|
||||
"sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7",
|
||||
"sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901",
|
||||
"sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c",
|
||||
"sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244",
|
||||
"sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6",
|
||||
"sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5",
|
||||
"sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e",
|
||||
"sha256:982f661bffc7a24b6d4f8ebe3291f17cf3833a0941c6f4d9d55c790b9aa2cdb3",
|
||||
"sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c",
|
||||
"sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0",
|
||||
"sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812",
|
||||
"sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a",
|
||||
"sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030",
|
||||
"sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==3.2.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"dateparser": {
|
||||
"hashes": [
|
||||
@@ -144,19 +139,19 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2",
|
||||
"sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"
|
||||
"sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
|
||||
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.4"
|
||||
"version": "==3.1.5"
|
||||
},
|
||||
"django-cors-headers": {
|
||||
"hashes": [
|
||||
"sha256:9322255c296d5f75089571f29e520c83ff9693df17aa3cf9f6a4bea7c6740169",
|
||||
"sha256:db82b2840f667d47872ae3e4a4e0a0d72fbecb42779b8aa233fa8bb965f7836a"
|
||||
"sha256:5665fc1b1aabf1b678885cf6f8f8bd7da36ef0a978375e767d491b48d3055d8f",
|
||||
"sha256:ba898dd478cd4be3a38ebc3d8729fa4d044679f8c91b2684edee41129d7e968a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
"version": "==3.6.0"
|
||||
},
|
||||
"django-extensions": {
|
||||
"hashes": [
|
||||
@@ -192,7 +187,8 @@
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7"
|
||||
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7",
|
||||
"sha256:0898182b4737a7b584a2c73735d89816343369f259fea932d90dc78e35d8ac33"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.12.2"
|
||||
@@ -223,19 +219,28 @@
|
||||
},
|
||||
"humanfriendly": {
|
||||
"hashes": [
|
||||
"sha256:175ffa628aa76da2c17369a5da5856084562cc66dfe7f82ae93ca3ef175277a6",
|
||||
"sha256:3c9ab8d28e88e6cc998e41963357736dafd555ee5bb666b50e42f6ce28dd3e3d"
|
||||
"sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d",
|
||||
"sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==9.0"
|
||||
"version": "==9.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:4a57a6379512ade94fa99e2fa46d3cd0f2f553040548d0e2958c6ed90ee48226",
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"imap-tools": {
|
||||
"hashes": [
|
||||
"sha256:72bf46dc135b039a5d5b59f4e079242ac15eac02a30038e8cb2dec7b153cab65",
|
||||
"sha256:75dc1c72dd76d9e577df26a1e0ec3a809b5eebce77678851458dcd2eae127ac9"
|
||||
"sha256:7d2d25b35117a3750c3b561dd93cc2fcb24cdc457830a049796c639f4371e317",
|
||||
"sha256:80088839cd1959f20c44206cdad4463ca1e7647ff67cf5b0e31e810fb6aaa6c4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.33.0"
|
||||
"version": "==0.34.0"
|
||||
},
|
||||
"img2pdf": {
|
||||
"hashes": [
|
||||
@@ -246,11 +251,11 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013",
|
||||
"sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"
|
||||
"sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771",
|
||||
"sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.1.1"
|
||||
"version": "==3.4.0"
|
||||
},
|
||||
"inotify-simple": {
|
||||
"hashes": [
|
||||
@@ -270,11 +275,11 @@
|
||||
},
|
||||
"joblib": {
|
||||
"hashes": [
|
||||
"sha256:698c311779f347cf6b7e6b8a39bb682277b8ee4aba8cf9507bc0cf4cd4737b72",
|
||||
"sha256:9e284edd6be6b71883a63c9b7f124738a3c16195513ad940eae7e3438de885d5"
|
||||
"sha256:75ead23f13484a2a414874779d69ade40d4fa1abe62b222a23cd50d4bc822f6f",
|
||||
"sha256:7ad866067ac1fdec27d51c8678ea760601b70e32ff1881d4dc8e1171f2b64b24"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.17.0"
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"langdetect": {
|
||||
"hashes": [
|
||||
@@ -332,67 +337,60 @@
|
||||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
"sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db",
|
||||
"sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce",
|
||||
"sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1",
|
||||
"sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512",
|
||||
"sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2",
|
||||
"sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757",
|
||||
"sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9",
|
||||
"sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2",
|
||||
"sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08",
|
||||
"sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b",
|
||||
"sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb",
|
||||
"sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc",
|
||||
"sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac",
|
||||
"sha256:5ddd1dfa2be066595c1993165b4cae84b9866b12339d0c903db7f21a094324a3",
|
||||
"sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83",
|
||||
"sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36",
|
||||
"sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387",
|
||||
"sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f",
|
||||
"sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad",
|
||||
"sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c",
|
||||
"sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414",
|
||||
"sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37",
|
||||
"sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764",
|
||||
"sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753",
|
||||
"sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909",
|
||||
"sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6",
|
||||
"sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63",
|
||||
"sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9",
|
||||
"sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949",
|
||||
"sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab",
|
||||
"sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c",
|
||||
"sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3",
|
||||
"sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893",
|
||||
"sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15",
|
||||
"sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"
|
||||
"sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94",
|
||||
"sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080",
|
||||
"sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e",
|
||||
"sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c",
|
||||
"sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76",
|
||||
"sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371",
|
||||
"sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c",
|
||||
"sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2",
|
||||
"sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a",
|
||||
"sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb",
|
||||
"sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140",
|
||||
"sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28",
|
||||
"sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f",
|
||||
"sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d",
|
||||
"sha256:6373751c4b6fd325606d29dd98dc2bf7092485ad20aafbfc6a177acd3b89059e",
|
||||
"sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff",
|
||||
"sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8",
|
||||
"sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa",
|
||||
"sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea",
|
||||
"sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc",
|
||||
"sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73",
|
||||
"sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d",
|
||||
"sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d",
|
||||
"sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4",
|
||||
"sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c",
|
||||
"sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e",
|
||||
"sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea",
|
||||
"sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd",
|
||||
"sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f",
|
||||
"sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff",
|
||||
"sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e",
|
||||
"sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7",
|
||||
"sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa",
|
||||
"sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827",
|
||||
"sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.19.4"
|
||||
"version": "==1.19.5"
|
||||
},
|
||||
"ocrmypdf": {
|
||||
"hashes": [
|
||||
"sha256:91e7394172cedb3be801a229dbd3d308fb5ae80cbc3a77879fa7954beea407b1",
|
||||
"sha256:e550b8e884150accab7ea41f4a576b5844594cb5cbd6ed514fbf1206720343ad"
|
||||
"sha256:416a9c4321bfc844f250694b8c68ebb538f60609bbc8686bd9f84a13c5127d68",
|
||||
"sha256:f45fc7e844e6026d6080a623a2936be120fc077d99aaa599df022acf35fb31e6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==11.3.4"
|
||||
},
|
||||
"pathtools": {
|
||||
"hashes": [
|
||||
"sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0",
|
||||
"sha256:d77d982475e87f32b82157a43b09f0a5ef3e66c1d8f3c7eb8d2580e783cd8202"
|
||||
],
|
||||
"version": "==0.1.2"
|
||||
"version": "==11.4.5"
|
||||
},
|
||||
"pathvalidate": {
|
||||
"hashes": [
|
||||
"sha256:1697c8ea71ff4c48e7aa0eda72fe4581404be8f41e51a17363ef682dd6824d35",
|
||||
"sha256:32d30dbacb711c16bb188b12ce7e9a46b41785f50a12f64500f747480a4b6ee3"
|
||||
"sha256:378c8b319838a255c00ab37f664686b75f0aabea4444d6c5a34effbec6738285",
|
||||
"sha256:cae8ad5cd9223c5c1f4bc4e2ef0cd4c5e89acd2d698fdb7610ee108b9be654d2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.0"
|
||||
"version": "==2.3.2"
|
||||
},
|
||||
"pdfminer.six": {
|
||||
"hashes": [
|
||||
@@ -411,65 +409,69 @@
|
||||
},
|
||||
"pikepdf": {
|
||||
"hashes": [
|
||||
"sha256:0829bd5dacd73bb4a37e7575bae523f49603479755563c92ddb55c206700cab1",
|
||||
"sha256:0d2b631077cd6af6e4d1b396208020705842610a6f13fab489d5f9c47916baa2",
|
||||
"sha256:21c98af08fae4ac9fbcad02b613b6768a4ca300fda4cba867f4a4b6f73c2d04b",
|
||||
"sha256:2240372fed30124ddc35b0c15a613f2b687a426ea2f150091e0a0c58cca7a495",
|
||||
"sha256:2a97f5f1403e058d217d7f6861cf51fca200c5687bce0d052f5f2fa89b5bfa22",
|
||||
"sha256:3faaefca0ae80d19891acec8b0dd5e6235f59f2206d82375eb80d090285e9557",
|
||||
"sha256:48ef45b64882901c0d69af3b85d16a19bd0f3e95b43e614fefb53521d8caf36c",
|
||||
"sha256:5212fe41f2323fc7356ba67caa39737fe13080562cff37bcbb74a8094076c8d0",
|
||||
"sha256:56859c32170663c57bd0658189ce44e180533eebe813853446cd6413810be9eb",
|
||||
"sha256:5f8fd1cb3478c5534222018aca24fbbd2bc74460c899bda988ec76722c13caa9",
|
||||
"sha256:74300a32c41b3d578772f6933f23a88b19f74484185e71e5225ce2f7ea5aea78",
|
||||
"sha256:8cbc946bdd217148f4a9c029fcea62f4ae0f67d5346de4c865f4718cd0ddc37f",
|
||||
"sha256:9ceefd30076f732530cf84a1be2ecb2fa9931af932706ded760a6d37c73b96ad",
|
||||
"sha256:ad69c170fda41b07a4c6b668a3128e7a759f50d9aebcfcde0ccff1358abe0423",
|
||||
"sha256:b715fe182189fb6870fab5b0383bb2fb278c88c46eade346b0f4c1ed8818c09d",
|
||||
"sha256:bb01ecf95083ffcb9ad542dc5342ccc1059e46f1395fd966629d36d9cc766b4a",
|
||||
"sha256:bd6328547219cf48cefb4e0a1bc54442910594de1c5a5feae847d9ff3c629031",
|
||||
"sha256:edb128379bb1dea76b5bdbdacf5657a6e4754bacc2049640762725590d8ed905",
|
||||
"sha256:f8e687900557fcd4c51b4e72b9e337fdae9e2c81049d1d80b624bb2e88b5769d",
|
||||
"sha256:fe0ca120e3347c851c34a91041d574f3c588d832023906d8ae18d66d042e8a52",
|
||||
"sha256:fe8e0152672f24d8bfdecc725f97e9013f2de1b41849150959526ca3562bd3ef"
|
||||
"sha256:0e67e5beeeed5422b3b8e862e4777fed5a4cd3c72e711e2a449a65d9ee641448",
|
||||
"sha256:138155ae1f71634cd6eca79f5517f77b2067ef0bd5b627ea9414e308fe868dc5",
|
||||
"sha256:15cf648dd760a47c55a4106b601b92bb653ae98155b10f04310553629c6695dd",
|
||||
"sha256:1d6a011ae4c501c78509caf19cbe152c2e3cb5c267f7b47bc3db8cd3436585a7",
|
||||
"sha256:211f529313953e44ae42eb896c2b688668385e6e8f9d04d21484bddb3c42b34c",
|
||||
"sha256:22049ad288d603a7fc68e90a0722770d307886788373ddfe71fbf614ced0f5b2",
|
||||
"sha256:24f7c371f6ecbee8f0ae30030992fc75cd32cd575dcfca8d466a03a8290377ca",
|
||||
"sha256:26cdf561632866d584fedb6b1c1fce78cefa49b5cae54c65aa6a6ca5fe6de4ac",
|
||||
"sha256:2c37afcd21a2eb1da1773687e853327fa8ec7d2c5cd90cdcd70180f55f0221e1",
|
||||
"sha256:65b8ec6403814f51e1b9c7e18a8ff26087fcc7a199b1405583e5ff9eb931db56",
|
||||
"sha256:66a03103aadb2e2738271cb18c89837ac3980fa0b4687195c4c150228b7e79de",
|
||||
"sha256:6e8f0124354c53a66f83ec5a18111b760aeff1a64db3a86e7ee5fed8e8624707",
|
||||
"sha256:70f2836cd468aa25bc8b09a2b9561364bd75d3e6ddb0e50a25d248d7da6cff25",
|
||||
"sha256:82cebf68952cfb65c86d880eb782a0c558b37531cdae59f2e11fcd0f2bb4669c",
|
||||
"sha256:84ad3e8fd5f3251fb5b534614da64b04a264ce9348f0fe35b781c0fb378b0f82",
|
||||
"sha256:af13fbc022efa85d1ae161129d4cde66493479db52b9adb74d525b890a078208",
|
||||
"sha256:c1d40fb8f8192c75f54f0e74a569ccf45e4e13bed8da78a78a5b488be29979bf",
|
||||
"sha256:d147ec1ab58512871fdf40a161809f698eaa75720b4a230198e7e028582b20a1",
|
||||
"sha256:dedad1f68d6b0b54000f7f99386351f1c6e19c8cf70a9700d8dd06b9809c54fb",
|
||||
"sha256:e72c3f5b624b9c7341fd6a7e657926d4cf12a7ea453681ffd7332cabc3530c62",
|
||||
"sha256:eb75f22e261b3bc69b6fc9a17b1d6966c95e79d3e792b7737a018a2bf6a2b07f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
"version": "==2.2.5"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a",
|
||||
"sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae",
|
||||
"sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce",
|
||||
"sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e",
|
||||
"sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140",
|
||||
"sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb",
|
||||
"sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021",
|
||||
"sha256:5a3342d34289715928c914ee7f389351eb37fa4857caa9297fc7948f2ed3e53d",
|
||||
"sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6",
|
||||
"sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302",
|
||||
"sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c",
|
||||
"sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271",
|
||||
"sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09",
|
||||
"sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3",
|
||||
"sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015",
|
||||
"sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3",
|
||||
"sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544",
|
||||
"sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8",
|
||||
"sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792",
|
||||
"sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0",
|
||||
"sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3",
|
||||
"sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8",
|
||||
"sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11",
|
||||
"sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7",
|
||||
"sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11",
|
||||
"sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e",
|
||||
"sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039",
|
||||
"sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5",
|
||||
"sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72"
|
||||
"sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6",
|
||||
"sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded",
|
||||
"sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865",
|
||||
"sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174",
|
||||
"sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032",
|
||||
"sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a",
|
||||
"sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e",
|
||||
"sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378",
|
||||
"sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17",
|
||||
"sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c",
|
||||
"sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913",
|
||||
"sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7",
|
||||
"sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0",
|
||||
"sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820",
|
||||
"sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba",
|
||||
"sha256:8c183b5c60544b49e0a66f924b18c526dfd37774811b627f70836fe01711abd3",
|
||||
"sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2",
|
||||
"sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b",
|
||||
"sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9",
|
||||
"sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234",
|
||||
"sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d",
|
||||
"sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5",
|
||||
"sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206",
|
||||
"sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9",
|
||||
"sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8",
|
||||
"sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59",
|
||||
"sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d",
|
||||
"sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7",
|
||||
"sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a",
|
||||
"sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0",
|
||||
"sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b",
|
||||
"sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d",
|
||||
"sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.0.1"
|
||||
"version": "==8.1.0"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
@@ -574,10 +576,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
|
||||
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
|
||||
],
|
||||
"version": "==2020.4"
|
||||
"version": "==2020.5"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
@@ -638,73 +640,90 @@
|
||||
},
|
||||
"reportlab": {
|
||||
"hashes": [
|
||||
"sha256:0008b5baa39d7e3a8132c4b47ecae88d6858ad386518e754e5e7b8025ee4722b",
|
||||
"sha256:0ad5a540c336941272fe161ef3a9830da3d4b3a65a195531cebd3cad5db58b2a",
|
||||
"sha256:0c965a5691686d746f558ee1c52aa9c63a01a0e13cba61ffc661573948e32f61",
|
||||
"sha256:0fd568fa5615ae99f76289c52ff230207852ee942d4934f6c893c93d2a79544e",
|
||||
"sha256:1117d905a3404c696869c7aabec9454b43ed6acbbc73f9256c6fcea23e7ae93e",
|
||||
"sha256:1ea7c388e91ad9d823655ad6a13751ff67e8a0e7cf4065cf051b4c931cdd9450",
|
||||
"sha256:26c0ee8f62652cc7fcdc47a1cb3b34775a4d625738025c1a7edb8718bda5a315",
|
||||
"sha256:368c5b3fc3d5a541cb9dcacefa563fdb445365f517e3cbf64b4326631d1cf13c",
|
||||
"sha256:451d42fdcdd7d84587d6d9c8f5d9a7d0e997305efb606705063ca1fe8bcca551",
|
||||
"sha256:47394acba4da8e56ef8e55d8eb483b868521696ba49ab0f0fcf8a1a4a5ac6e49",
|
||||
"sha256:51b16e297f7b937fc530dd151e4b38f1d305b01c9aa10657bc32a5d2901b8ad7",
|
||||
"sha256:51c0cdcf606ded0a7b4b50050400f25125ea797fbfc3c817135993b38f8b764e",
|
||||
"sha256:55c672c579618843e0fd00140fb71f1ffebc4f1c542ac385c4f4999f2f5398d9",
|
||||
"sha256:5c34a96ecfbf595caf16178a06abcd26a5f8720e01fe1285d4c97333382cfaeb",
|
||||
"sha256:61aa89a00754b18c4f2956b8bff831f1fd3affef6476dc63462d92211941605e",
|
||||
"sha256:62234d29c97279917903e4587faf240a5dea4617be250db55386ff268eb5a7c5",
|
||||
"sha256:670f2a8dcc23bf798c39b95c64bf76ee387549b962f76783670821978a226663",
|
||||
"sha256:69387f171f6c7b55109caa6d061b17a18f2f9e724a0212c07cd692aeb369dd19",
|
||||
"sha256:6c5c8871b659f7c2975382d7b61f3c182701fa9eb62cf649c3c73ba8fc5e2595",
|
||||
"sha256:80139ceb3a568f5be908094f1701fd05391b71425e8b69aaed0d30db647ca2aa",
|
||||
"sha256:80661a76d0019b5e2c315ccd3bc7093d754067d6142b36a3a0ec4f416073d23b",
|
||||
"sha256:85a2236f324ae336da7f4b183fa99bed261bcc00ac1255ee91a504e68b086d00",
|
||||
"sha256:89a3acd98bd4478d6bbc5cb32e0665ea546c98bff8b58d5e1014659daa6ef75a",
|
||||
"sha256:8a39119fcab146bde41fd1c6d148f9ee1e2cca10c6f9c2b7eb4dd710a3a2c6ac",
|
||||
"sha256:9c31c2526401da6cc92018f68483f2aac0a731cb98435445ea4b72d46b438c84",
|
||||
"sha256:9e8ae1c3b8a1697147c5c97f00d66ab1c54d88c4615b0cdd9b1a667d7baf3eb7",
|
||||
"sha256:a479c38ab2b997ce05d3bef906783ac20cf4cb224a154e80c9018c5e4d943a35",
|
||||
"sha256:a79aab8d069543d5085d58260f18705a08acd92a4501a41261913fddc2137d46",
|
||||
"sha256:b0a8314383de853599ca531dfe55eaa49bb8d6b0bb663b2f8479b7a0f3385ea2",
|
||||
"sha256:b3d9926e64bd8008007b2d9819d7b30179b069ce95431d5060f71afc36885389",
|
||||
"sha256:c2a9a77ce4f25ffb52d705be82a9f41b47f6b0da23870ebc3587709e7242da30",
|
||||
"sha256:c578dd0799f70fb577474cd383f035c6e1057e4fe837278113f9cfa6eee4b076",
|
||||
"sha256:c5abd9d0023ad20030524ab0d5fa39d77aed025519b1fa426304ab2dd0328b89",
|
||||
"sha256:ced96125525ba21311e9512adf391170b9e149f89e27e45b06ff07b70f97a0b2",
|
||||
"sha256:d692fb88d6ef5e75242b00009b54953a0425eaa8bd3a36db9db8b396785e1f57",
|
||||
"sha256:d70c2104286459658e61388af9eee838b612986bd8a36e1d21ba36152983ac15",
|
||||
"sha256:de47c65c10ac6f0d2addb28f1b1657b1c707aca014d09d01b3b728cf19e8f791",
|
||||
"sha256:e6e7592527791841db0820a72c6afae52655a05b0b6d4df184fd2bafe82ee1ee",
|
||||
"sha256:e8a7e95ee6ea5566291b59ede5b9fadce809dca43ebfbfe11e3ff3d6492c6f0e",
|
||||
"sha256:f041759138b3a95508c4281b3db3bf9bb28636d84c554272a58a5ca7c9f9bbf4",
|
||||
"sha256:f39c7fc1fa2e4a1d9747a3effd70731a9d0e9eb5738247fa089c059eff19d43e",
|
||||
"sha256:f65ac89ee0ba569f5279360eae08783f7f2e95c9810a9846c957fbd5950f4896"
|
||||
"sha256:009fa61710647cdc62eb373345248d8ebb93583a058990f7c4f9be46d90aa5b1",
|
||||
"sha256:04a08d284da86882ec3a41a7c719833362ef891b09ee8e2fbb47cee352aa684a",
|
||||
"sha256:07bff6742fba612da8d1b1f783c436338c6fdc6962828159827d5ca7d2b67935",
|
||||
"sha256:09fb11ab1500e679fc1b01199d2fed24435499856e75043a9ac0d31dd48fd881",
|
||||
"sha256:18a876449c9000c391dd3415ebc8454cd7bb9e488977b894886a2d7d018f16cd",
|
||||
"sha256:18eec161411026dde49767bee4e5e8eeb8014879554811a62581dc7433628d5b",
|
||||
"sha256:19353aead39fc115a4d6c598d6fb9fa26da7e69160a0443ebb49b02903e704e8",
|
||||
"sha256:1b85c20e89c22ae902ca973df2afdd2d64d27dc4ffd2b29ebad8c805a213756b",
|
||||
"sha256:1da3d7a35f918cee905facfa94bd00ae6091cadc06dca1b0b31b69ae02d41d1d",
|
||||
"sha256:1e484ce83dae26cb40fcbd312d45b8ba921de7856a00339d867dd4ecf145a1e7",
|
||||
"sha256:33f3cfdc492575f8af3225701301a7e62fc478358729820c9e0091aff5831378",
|
||||
"sha256:3b0026c1129147befd4e5a8cf25da8dea1096fce371e7b2412e36d7254019c06",
|
||||
"sha256:3d7713dddaa8081ed709a1fa2456a43f6a74b0f07d605da8441fd53fef334f69",
|
||||
"sha256:3e2b4d69763103b9dc9b54c0952dc3cee05cedd06e28c0987fad7f84705b12c0",
|
||||
"sha256:4ca5233a19a5ceca23546290f43addec2345789c7d65bb32f8b2668aa148351f",
|
||||
"sha256:5214a289cf01ebbd65e49bae83709671dd9edb601891cf0ae8abf85f3c0b392f",
|
||||
"sha256:52f8237654acbc78ea2fa6fb4a6a06e5b023b6da93f7889adfe2deba09473fad",
|
||||
"sha256:5ed00894e0f8281c0b7c0494b4d3067c641fd90c8e5cf933089ec4cc9a48e491",
|
||||
"sha256:6191961533d49c9d860964d42bada4d7ac3bb28502d984feb8034093f2012fa8",
|
||||
"sha256:6f3ad2b1afe99c436563cd436d8693d4a12e2c4bd45f70c7705759ff7837fe53",
|
||||
"sha256:739b743b7ca1ba4b4d64c321de6fccb49b562d0507ea06c817d9cc4faed5cd22",
|
||||
"sha256:792efba0c0c6e4ee94f6dc95f305451733ee9230a1c7d51cb8e5301a549e0dfb",
|
||||
"sha256:79d63ca40231ca3860859b39a92daa5219035ba9553da89a5e1b218550744121",
|
||||
"sha256:83b28104edd58ad65748d2d0e60e0d97e3b91b3e90b4573ea6fe60de6811972c",
|
||||
"sha256:85650446538cd2f606ca234634142a7ccd74cb6db7cfec250f76a4242e0f2431",
|
||||
"sha256:8850eba6de6eb813036eb8dce353e40d60c8af48bbce107de82770b10d3aa525",
|
||||
"sha256:9da445cb79e3f740756924c053edc952cde11a65ff5af8acfda3c0a1317136ef",
|
||||
"sha256:9fabd5fbd24f5971085ffe53150d663f158f7d3050b25c95736e29ebf676d454",
|
||||
"sha256:a0c377bc45e73c3f15f55d7de69fab270d174749d5b454ab0de502b15430ec2a",
|
||||
"sha256:a1d3f7022a920d4a5e165d264581f1862e1c1b877ceeabb96fe98cec98125ae5",
|
||||
"sha256:a315edef5c5610b0c75790142f49487e89ea34397fc247ae8aa890fe6d6dd057",
|
||||
"sha256:a755cca2dcf023130b03bb671670301a992157d5c3151d838c0b68ef89894536",
|
||||
"sha256:b1b20208ecdfffd7ca027955c4fe8972b28b30a4b3b80cf25099a08d3b20ed7c",
|
||||
"sha256:b26d6f416891cef93411d6d478a25db275766081a5fb66368248293ef459f3be",
|
||||
"sha256:b4ba4c30af7044ee987e61c88a5ffb76031ca0c53666bc85d823b7de55ddbc75",
|
||||
"sha256:b71faf3b6e4d7058e1af1b8afedaf39a962db4a219affc8177009d8244ec10d4",
|
||||
"sha256:cfa854bea525f8c913cb77e2bda724d94b965a0eb3bcfc4a645a9baa29bb86e2",
|
||||
"sha256:dd9687359e466086b9f6fe6d8069034017f8b6ca3080944fae5709767ca6814e",
|
||||
"sha256:de0c675fc2998a7eaa929c356ba49c84f53a892e9ab25e8ee7d8ebbbdcb2ac16",
|
||||
"sha256:e2b4e33fea2ce9d3a14ea39191b169e41eb2ac995274f54ac8fd27519974bce8",
|
||||
"sha256:f3d4a1a273dc141e03b72a553c11bc14dd7a27ec7654a071edcf83eb04f004bc",
|
||||
"sha256:ff547cf4c1de7e104cad1a378431ff81efcb03e90e40871ee686107da5b91442"
|
||||
],
|
||||
"version": "==3.5.56"
|
||||
"version": "==3.5.59"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"scikit-learn": {
|
||||
"hashes": [
|
||||
"sha256:090bbf144fd5823c1f2efa3e1a9bf180295b24294ca8f478e75b40ed54f8036e",
|
||||
"sha256:0a127cc70990d4c15b1019680bfedc7fec6c23d14d3719fdf9b64b22d37cdeca",
|
||||
"sha256:0d39748e7c9669ba648acf40fb3ce96b8a07b240db6888563a7cb76e05e0d9cc",
|
||||
"sha256:1b8a391de95f6285a2f9adffb7db0892718950954b7149a70c783dc848f104ea",
|
||||
"sha256:20766f515e6cd6f954554387dfae705d93c7b544ec0e6c6a5d8e006f6f7ef480",
|
||||
"sha256:2aa95c2f17d2f80534156215c87bee72b6aa314a7f8b8fe92a2d71f47280570d",
|
||||
"sha256:5ce7a8021c9defc2b75620571b350acc4a7d9763c25b7593621ef50f3bd019a2",
|
||||
"sha256:6c28a1d00aae7c3c9568f61aafeaad813f0f01c729bee4fd9479e2132b215c1d",
|
||||
"sha256:7671bbeddd7f4f9a6968f3b5442dac5f22bf1ba06709ef888cc9132ad354a9ab",
|
||||
"sha256:914ac2b45a058d3f1338d7736200f7f3b094857758895f8667be8a81ff443b5b",
|
||||
"sha256:98508723f44c61896a4e15894b2016762a55555fbf09365a0bb1870ecbd442de",
|
||||
"sha256:a64817b050efd50f9abcfd311870073e500ae11b299683a519fbb52d85e08d25",
|
||||
"sha256:cb3e76380312e1f86abd20340ab1d5b3cc46a26f6593d3c33c9ea3e4c7134028",
|
||||
"sha256:d0dcaa54263307075cb93d0bee3ceb02821093b1b3d25f66021987d305d01dce",
|
||||
"sha256:d9a1ce5f099f29c7c33181cc4386660e0ba891b21a60dc036bf369e3a3ee3aec",
|
||||
"sha256:da8e7c302003dd765d92a5616678e591f347460ac7b53e53d667be7dfe6d1b10",
|
||||
"sha256:daf276c465c38ef736a79bd79fc80a249f746bcbcae50c40945428f7ece074f8"
|
||||
"sha256:076369634ee72b5a5941440661e2f306ff4ac30903802dc52031c7e9199ac640",
|
||||
"sha256:18f7131e62265bf2691ed1d0303c640313894ccfe4278427478c6b2f45094b53",
|
||||
"sha256:26f66b3726b54dfb76ea51c5d9c2431ed17ebc066cb4527662b9e851a3e7ba61",
|
||||
"sha256:2951f87d35e72f007701c6e028aa230f6df6212a3194677c0c950486066a454d",
|
||||
"sha256:2a5348585aa793bc8cc5a72f8e9067c9380834b0aadbd55f924843b071f13282",
|
||||
"sha256:3eeff086f7329521d27249a082ea3c48c085cedb110db5f65968ab55c3ba2e09",
|
||||
"sha256:4395e91b3548005f4a645018435b5a94f8cce232b5b70753020e606c6a750656",
|
||||
"sha256:44e452ea8491225c5783d49577aad0f36202dfd52aec7f82c0fdfe5fbd5f7400",
|
||||
"sha256:490436b44b3a1957cb625e871764b0aa330b34cc416aea4abc6c38ca63d0d682",
|
||||
"sha256:5e6e3c042cea83f2e20a45e563b8eabc1f8f72446251fe23ebefdf111a173a33",
|
||||
"sha256:66f27bf21202a850bcd7b6303916e4907f6e22ec59a14974ede4955aed5c7ed0",
|
||||
"sha256:743b6edd98c98991be46c08e6b21df3861d5ae915f91d59f988384d93f7263e7",
|
||||
"sha256:758619e49cd7c17282e6cc60d5cc73c02c072b47c9a10010bb3bb47e0d976e50",
|
||||
"sha256:7f654befc5ad413690cc58f3f34a3e906caf825195ce0fda00a8e9565e1403e6",
|
||||
"sha256:800aaf63f8838c00e85db2267dd226f89858594843fd03932a9eda95746d2c40",
|
||||
"sha256:80ca024154b84b6ac4cfc86930ba13fdc348a209753bf2c16129db6f9eb8a80b",
|
||||
"sha256:890d7d588f65acb0c4f6c083347c9076916bda5e6bd8400f06244b1afc1009af",
|
||||
"sha256:905d8934d1e27a686698864a5863ff2c0e13a2ae1adb78a8a848aacc8a49927d",
|
||||
"sha256:a83fcd9d59c42a2f66b307e3b0b0f08aa8e6e45be33da055697ea499f0e4f7c2",
|
||||
"sha256:afeb06dc69847927634e58579b9cdc72e1390b79497336b2324b1b173f33bd47",
|
||||
"sha256:b0d13fd56d26cf3de0314a4fd48037108c638fe126d813f5c1222bb0f08b6a76",
|
||||
"sha256:c08b27cb78ee8d2dc781a7affed09859441f5b624f9f92da59ac0791c8774dfc",
|
||||
"sha256:c912247e42114f389858ae05d63f4359d4e667ea72aaabee191aee9ad3f9774a",
|
||||
"sha256:d7fe05fcb44eadd6d6c874c768f085f5de1239db3a3b7be4d3d23d12e4120589",
|
||||
"sha256:d819d625832fb2969911a243e009cfa135cb8ef1e150866e417d6e9d75290087",
|
||||
"sha256:e534f5f3796db6781c87e9835dcd51b7854c8c5a379c9210b93605965c1941fd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.23.2"
|
||||
"version": "==0.24.0"
|
||||
},
|
||||
"scipy": {
|
||||
"hashes": [
|
||||
@@ -769,13 +788,30 @@
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"tqdm": {
|
||||
"tika": {
|
||||
"hashes": [
|
||||
"sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5",
|
||||
"sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"
|
||||
"sha256:c2c50f405622f74531841104f9e85c17511aede11de8e5385eab1a29a31f191b",
|
||||
"sha256:d1f2eddb93caa9a2857569486aa2bc0320d0bf1796cdbe03066954cbc4b4bf62"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.54.1"
|
||||
"version": "==1.24"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a",
|
||||
"sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.56.0"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
|
||||
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
||||
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.7.4.3"
|
||||
},
|
||||
"tzlocal": {
|
||||
"hashes": [
|
||||
@@ -784,13 +820,36 @@
|
||||
],
|
||||
"version": "==2.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
|
||||
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.2"
|
||||
},
|
||||
"watchdog": {
|
||||
"hashes": [
|
||||
"sha256:3caefdcc8f06a57fdc5ef2d22aa7c0bfda4f55e71a0bee74cbf3176d97536ef3",
|
||||
"sha256:e38bffc89b15bafe2a131f0e1c74924cf07dcec020c2e0a26cccd208831fcd43"
|
||||
"sha256:016b01495b9c55b5d4126ed8ae75d93ea0d99377084107c33162df52887cee18",
|
||||
"sha256:101532b8db506559e52a9b5d75a308729b3f68264d930670e6155c976d0e52a0",
|
||||
"sha256:27d9b4666938d5d40afdcdf2c751781e9ce36320788b70208d0f87f7401caf93",
|
||||
"sha256:2f1ade0d0802503fda4340374d333408831cff23da66d7e711e279ba50fe6c4a",
|
||||
"sha256:376cbc2a35c0392b0fe7ff16fbc1b303fd99d4dd9911ab5581ee9d69adc88982",
|
||||
"sha256:57f05e55aa603c3b053eed7e679f0a83873c540255b88d58c6223c7493833bac",
|
||||
"sha256:5f1f3b65142175366ba94c64d8d4c8f4015825e0beaacee1c301823266b47b9b",
|
||||
"sha256:602dbd9498592eacc42e0632c19781c3df1728ef9cbab555fab6778effc29eeb",
|
||||
"sha256:68744de2003a5ea2dfbb104f9a74192cf381334a9e2c0ed2bbe1581828d50b61",
|
||||
"sha256:85e6574395aa6c1e14e0f030d9d7f35c2340a6cf95d5671354ce876ac3ffdd4d",
|
||||
"sha256:b1d723852ce90a14abf0ec0ca9e80689d9509ee4c9ee27163118d87b564a12ac",
|
||||
"sha256:d948ad9ab9aba705f9836625b32e965b9ae607284811cd98334423f659ea537a",
|
||||
"sha256:e2a531e71be7b5cc3499ae2d1494d51b6a26684bcc7c3146f63c810c00e8a3cc",
|
||||
"sha256:e7c73edef48f4ceeebb987317a67e0080e5c9228601ff67b3c4062fa020403c7",
|
||||
"sha256:ee21aeebe6b3e51e4ba64564c94cee8dbe7438b9cb60f0bb350c4fa70d1b52c2",
|
||||
"sha256:f1d0e878fd69129d0d68b87cee5d9543f20d8018e82998efb79f7e412d42154a",
|
||||
"sha256:f84146f7864339c8addf2c2b9903271df21d18d2c721e9a77f779493234a82b5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.4"
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
@@ -873,61 +932,76 @@
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '3.1'",
|
||||
"version": "==3.0.4"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
|
||||
"sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
|
||||
"sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
|
||||
"sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
|
||||
"sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
|
||||
"sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
|
||||
"sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
|
||||
"sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
|
||||
"sha256:3188a7dfd96f734a7498f37cde6598b1e9c084f1ca68bc1aa04e88db31168ab6",
|
||||
"sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
|
||||
"sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
|
||||
"sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
|
||||
"sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
|
||||
"sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
|
||||
"sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
|
||||
"sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
|
||||
"sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
|
||||
"sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
|
||||
"sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
|
||||
"sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
|
||||
"sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
|
||||
"sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
|
||||
"sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
|
||||
"sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
|
||||
"sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
|
||||
"sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
|
||||
"sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
|
||||
"sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
|
||||
"sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
|
||||
"sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
|
||||
"sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
|
||||
"sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
|
||||
"sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
|
||||
"sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
|
||||
"sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8",
|
||||
"sha256:ef221855191457fffeb909d5787d1807800ab4d0111f089e6c93ee68f577634d"
|
||||
"sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297",
|
||||
"sha256:262066798d786ad67a13c7ba869e3ce0e39609f99f6d6c80160ad602c4808e32",
|
||||
"sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1",
|
||||
"sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497",
|
||||
"sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606",
|
||||
"sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528",
|
||||
"sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b",
|
||||
"sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4",
|
||||
"sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830",
|
||||
"sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1",
|
||||
"sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f",
|
||||
"sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d",
|
||||
"sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3",
|
||||
"sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8",
|
||||
"sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500",
|
||||
"sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7",
|
||||
"sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb",
|
||||
"sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b",
|
||||
"sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059",
|
||||
"sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b",
|
||||
"sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72",
|
||||
"sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36",
|
||||
"sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277",
|
||||
"sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c",
|
||||
"sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631",
|
||||
"sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff",
|
||||
"sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8",
|
||||
"sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec",
|
||||
"sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b",
|
||||
"sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7",
|
||||
"sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105",
|
||||
"sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b",
|
||||
"sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c",
|
||||
"sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b",
|
||||
"sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98",
|
||||
"sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4",
|
||||
"sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879",
|
||||
"sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f",
|
||||
"sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4",
|
||||
"sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044",
|
||||
"sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e",
|
||||
"sha256:eb33c4c858d06bd8d79713c7628d3f2b50fb1c62071e2e88cb44876be03eabe1",
|
||||
"sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899",
|
||||
"sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f",
|
||||
"sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448",
|
||||
"sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714",
|
||||
"sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2",
|
||||
"sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d",
|
||||
"sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd",
|
||||
"sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7",
|
||||
"sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==5.3"
|
||||
"version": "==5.3.1"
|
||||
},
|
||||
"coveralls": {
|
||||
"hashes": [
|
||||
"sha256:2301a19500b06649d2ec4f2858f9c69638d7699a4c63027c5d53daba666147cc",
|
||||
"sha256:b990ba1f7bc4288e63340be0433698c1efe8217f78c689d254c2540af3d38617"
|
||||
"sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434",
|
||||
"sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"distlib": {
|
||||
"hashes": [
|
||||
@@ -961,19 +1035,19 @@
|
||||
},
|
||||
"factory-boy": {
|
||||
"hashes": [
|
||||
"sha256:d8626622550c8ba31392f9e19fdbcef9f139cf1ad643c5923f20490a7b3e2e3d",
|
||||
"sha256:ded73e49135c24bd4d3f45bf1eb168f8d290090f5cf4566b8df3698317dc9c08"
|
||||
"sha256:1d3db4b44b8c8c54cdd8b83ae4bdb9aeb121e464400035f1f03ae0e1eade56a4",
|
||||
"sha256:401cc00ff339a022f84d64a4339503d1689e8263a4478d876e58a3295b155c5b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.0"
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"faker": {
|
||||
"hashes": [
|
||||
"sha256:1fcb415562ee6e2395b041e85fa6901d4708d30b84d54015226fa754ed0822c3",
|
||||
"sha256:e8beccb398ee9b8cc1a91d9295121d66512b6753b4846eb1e7370545d46b3311"
|
||||
"sha256:47ac7d62d5bad8c16422a91f121430ab7656d40ca8fea9c84bcdbdf92e739b03",
|
||||
"sha256:6bc44606d44f711e1d89ad9a5b42394cc6f7eedaffc765ddb5b2d22084c15733"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.1"
|
||||
"version": "==5.5.0"
|
||||
},
|
||||
"filelock": {
|
||||
"hashes": [
|
||||
@@ -1002,19 +1076,19 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013",
|
||||
"sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"
|
||||
"sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771",
|
||||
"sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.1.1"
|
||||
"version": "==3.4.0"
|
||||
},
|
||||
"importlib-resources": {
|
||||
"hashes": [
|
||||
"sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592",
|
||||
"sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"
|
||||
"sha256:4743f090ed8946e713745ec0e660249ef9fb0b9843eacc5b5ff931d2fd5aa67f",
|
||||
"sha256:ea17df80a0ff04b5dbd3d96dbeab1842acfd1c6c902eaeb8c8858abf2720161e"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==3.3.0"
|
||||
"version": "==5.0.0"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
@@ -1077,11 +1151,11 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236",
|
||||
"sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"
|
||||
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
|
||||
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.7"
|
||||
"version": "==20.8"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
@@ -1093,11 +1167,11 @@
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
|
||||
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
|
||||
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.9.0"
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
@@ -1109,11 +1183,11 @@
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716",
|
||||
"sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"
|
||||
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
|
||||
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.7.3"
|
||||
"version": "==2.7.4"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
@@ -1125,11 +1199,11 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe",
|
||||
"sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"
|
||||
"sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
|
||||
"sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.1.2"
|
||||
"version": "==6.2.1"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
@@ -1174,11 +1248,11 @@
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:7c629016b3bb006b88ac68e2b31551e7becf173c76b977768848e2bbed594d90",
|
||||
"sha256:82d938f1a24186520e2d9d3a64ef7d9ac7ecdf1a0659e095d18e596b8cbd0672"
|
||||
"sha256:1d8edbb1a45e8e1f8e44b1260583107fc23f8bc8da6d18cb331ff61d41258ecf",
|
||||
"sha256:f127e11e84ad37cc1de1088cb2990f3c354630d428af3f71282de589c5bb779b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.0"
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
@@ -1190,18 +1264,18 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
|
||||
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
|
||||
],
|
||||
"version": "==2020.4"
|
||||
"version": "==2020.5"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
|
||||
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.25.0"
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
@@ -1220,19 +1294,19 @@
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:1e8d592225447104d1172be415bc2972bd1357e3e12fdc76edf2261105db4300",
|
||||
"sha256:d4e59ad4ea55efbb3c05cde3bfc83bfc14f0c95aa95c3d75346fcce186a47960"
|
||||
"sha256:41cad293f954f7d37f803d97eb184158cfd90f51195131e94875bc07cd08b93c",
|
||||
"sha256:c314c857e7cd47c856d2c5adff514ac2e6495f8b8e0f886a8a37e9305dfea0d8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.3.1"
|
||||
"version": "==3.4.3"
|
||||
},
|
||||
"sphinx-rtd-theme": {
|
||||
"hashes": [
|
||||
"sha256:22c795ba2832a169ca301cd0a083f7a434e09c538c70beb42782c073651b707d",
|
||||
"sha256:373413d0f82425aaa28fb288009bf0d0964711d347763af2f1b65cafcb028c82"
|
||||
"sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5",
|
||||
"sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.0"
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
@@ -1306,11 +1380,20 @@
|
||||
},
|
||||
"tox": {
|
||||
"hashes": [
|
||||
"sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2",
|
||||
"sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6"
|
||||
"sha256:5efda30ad73e662c3844ac51ce1381bf28f61063773e06996aa8b6277133a7c0",
|
||||
"sha256:8cccede64802e78aa6c69f81051b25f0706639d1cbbb34d9366ce00c70ee054f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.20.1"
|
||||
"version": "==3.21.0"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
|
||||
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
||||
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.7.4.3"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
@@ -1322,11 +1405,11 @@
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c",
|
||||
"sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b"
|
||||
"sha256:205a7577275dd0d9223c730dd498e21a8910600085c3dee97412b041fc4b853b",
|
||||
"sha256:7992b8de87e544a4ab55afc2240bf8388c4e3b5765d03784dad384bfdf9097ee"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.2.2"
|
||||
"version": "==20.3.0"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
|
67
README.md
67
README.md
@@ -1,13 +1,14 @@
|
||||
[](https://travis-ci.org/jonaswinkler/paperless-ng)
|
||||

|
||||
[](https://paperless-ng.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://gitter.im/paperless-ng/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://hub.docker.com/r/jonaswinkler/paperless-ng)
|
||||
[](https://coveralls.io/github/jonaswinkler/paperless-ng?branch=master)
|
||||
|
||||
# Paperless-ng
|
||||
|
||||
[Paperless](https://github.com/the-paperless-project/paperless) is an application by Daniel Quinn and others that indexes your scanned documents and allows you to easily search for documents and store metadata alongside your documents.
|
||||
[Paperless](https://github.com/the-paperless-project/paperless) is an application by Daniel Quinn and contributors that indexes your scanned documents and allows you to easily search for documents and store metadata alongside your documents.
|
||||
|
||||
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. For a detailed list of changes, see below.
|
||||
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. For a detailed list of changes, have a look at the changelog in the documentation.
|
||||
|
||||
This project is still in development and some things may not work as expected.
|
||||
|
||||
@@ -15,11 +16,13 @@ This project is still in development and some things may not work as expected.
|
||||
|
||||
Paperless does not control your scanner, it only helps you deal with what your scanner produces.
|
||||
|
||||
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.
|
||||
2. 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.
|
||||
3. Have the target server run the Paperless consumption script to OCR the file and index it into a local database.
|
||||
4. Use the web frontend to sift through the database and find what you want.
|
||||
5. Download the PDF you need/want via the web interface and do whatever you like with it. You can even print it and send it as if it's the original. In most cases, no one will care or notice.
|
||||
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.
|
||||
|
||||
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.
|
||||
4. Download the PDF you need/want via the web interface and do whatever you like with it. You can even print it and send it as if it's the original. In most cases, no one will care or notice.
|
||||
|
||||
Here's what you get:
|
||||
|
||||
@@ -28,20 +31,22 @@ Here's what you get:
|
||||
# Features
|
||||
|
||||
* Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents.
|
||||
* Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and can be configured freely.
|
||||
* Single page application front end. Should be pretty snappy. Will be mobile friendly in the future.
|
||||
* Includes a dashboard that shows basic statistics and has document upload.
|
||||
* Filtering by tags, correspondents, types, and more.
|
||||
* Customizable views can be saved and displayed on the dashboard.
|
||||
* Full text search with auto completion, scored results and query highlighting allows you to quickly find what you need.
|
||||
* Full text search helps you find what you need.
|
||||
* Auto completion suggests relevant words from your documents.
|
||||
* Results are sorted by relevance to your search query.
|
||||
* Highlighting shows you which parts of the document matched the query.
|
||||
* Searching for similar documents ("More like this")
|
||||
* Email processing: Paperless adds documents from your email accounts.
|
||||
* Configure multiple accounts and filters for each account.
|
||||
* When adding documents from mails, paperless can move these mails to a new folder, mark them as read, flag them or delete them.
|
||||
* Machine learning powered document matching.
|
||||
* Paperless learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless.
|
||||
* We have a mobile app that offers a 'Share with paperless' option over at https://github.com/qcasey/paperless_share. You can use that in combination with any of the mobile scanning apps out there. It's still a little rough around the edges, but it works!
|
||||
* A task processor that processes documents in parallel and also tells you when something goes wrong. On modern multi core systems, consumption is blazing fast.
|
||||
* Code cleanup in many, MANY areas. Some of the code from OG paperless was just overly complicated.
|
||||
* More tests, more stability.
|
||||
|
||||
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).
|
||||
|
||||
@@ -50,34 +55,43 @@ For a complete list of changes from paperless, check out the [changelog](https:/
|
||||
# Roadmap for 1.0
|
||||
|
||||
- Make the front end nice (except mobile).
|
||||
- Test coverage at 90%.
|
||||
- Fix whatever bugs I and you find.
|
||||
- Make the documentation nice.
|
||||
|
||||
## Roadmap for versions beyond 1.0
|
||||
|
||||
These are things that I want to add to paperless eventually. They are sorted by priority.
|
||||
|
||||
- **Bulk editing**. Add/remove metadata from multiple documents at once.
|
||||
- **More search.** The search backend is incredibly versatile and customizable. Searching is the most important feature of this project and thus, I want to implement things like:
|
||||
- Group and limit search results by correspondent, show “more from this” links in the results.
|
||||
- Ability to search for “Similar documents” in the search results
|
||||
- **Nested tags**. Organize tags in a hierarchical structure. This will combine the benefits of folders and tags in one coherent system.
|
||||
- **An interactive consumer** that shows its progress for documents it processes on the web page.
|
||||
- With live updates ans websockets. This already works on a dev branch, but requires a lot of new dependencies, which I'm not particular happy about.
|
||||
- With live updates and websockets. This already works on a dev branch, but requires a lot of new dependencies, which I'm not particularly happy about.
|
||||
- Notifications when a document was added with buttons to open the new document right away.
|
||||
- **Arbitrary tag colors**. Allow the selection of any color with a color picker.
|
||||
|
||||
Apart from that, paperless is pretty much feature complete.
|
||||
|
||||
## On the chopping block.
|
||||
|
||||
- **GnuPG encrypion.** [Here's a note about encryption in paperless](https://paperless-ng.readthedocs.io/en/latest/administration.html#managing-encryption). The gist of it is that I don't see which attacks this implementation protects against. It gives a false sense of security to users who don't care about how it works.
|
||||
|
||||
## Wont-do list.
|
||||
|
||||
These features will probably never make it into paperless, since paperless is meant to be an easy to use set-and-forget solution.
|
||||
|
||||
- **Document versions.** I might consider adding the ability to update a document with a newer version, but that's about it. The kind of documents that get added to paperless usually don't change at all.
|
||||
- **Workflows.** I don't see a use case for these, yet.
|
||||
- **Folders.** Tags are superior in just about every way.
|
||||
- **Apps / extension support.** Again, paperless is meant to be simple.
|
||||
|
||||
# Getting started
|
||||
|
||||
The recommended way to deploy paperless is docker-compose. Don't clone the repository, grab the latest release to get started instead. The dockerfiles archive contains just the docker files which will pull the image from docker hub. The source archive contains everything you need to build the docker image yourself (i.e. if you want to run on Raspberry Pi).
|
||||
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 information about the individual components of paperless that you need to take care of.
|
||||
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.
|
||||
|
||||
# Migrating to paperless-ng
|
||||
|
||||
@@ -87,6 +101,12 @@ Read the section about [migration](https://paperless-ng.readthedocs.io/en/latest
|
||||
|
||||
The documentation for Paperless-ng is available on [ReadTheDocs](https://paperless-ng.readthedocs.io/).
|
||||
|
||||
# Translation
|
||||
|
||||
Paperless is currently available in English, German, Dutch and French. Translation is coordinated at transifex: https://www.transifex.com/paperless/paperless-ng
|
||||
|
||||
If you want to see paperless in your own language, request that language at transifex and you can start translating after I approve the language.
|
||||
|
||||
# Suggestions? Questions? Something not working?
|
||||
|
||||
Please open an issue and start a discussion about it!
|
||||
@@ -101,12 +121,13 @@ If you want to implement something big: Please start a discussion about that in
|
||||
|
||||
Paperless has been around a while now, and people are starting to build stuff on top of it. If you're one of those people, we can add your project to this list:
|
||||
|
||||
* [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless. We're working on making this compatible.
|
||||
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows.
|
||||
* [ansible-role-paperless](https://github.com/ovv/ansible-role-paperless): An easy way to get Paperless running via Ansible.
|
||||
* [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance.
|
||||
* [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless. Updated to work with paperless-ng.
|
||||
* [Paperless Share](https://github.com/qcasey/paperless_share). Share any files from your Android application with paperless. Very simple, but works with all of the mobile scanning apps out there that allow you to share scanned documents.
|
||||
|
||||
Compatibility with Paperless-ng is unknown.
|
||||
These projects also exist, but their status and compatibility with paperless-ng is unknown.
|
||||
|
||||
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows.
|
||||
* [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance.
|
||||
|
||||
# Important Note
|
||||
|
||||
|
38
ansible/README.md
Normal file
38
ansible/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
Role Name
|
||||
=========
|
||||
|
||||
A brief description of the role goes here.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
|
||||
|
||||
Role Variables
|
||||
--------------
|
||||
|
||||
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
|
||||
|
||||
Example Playbook
|
||||
----------------
|
||||
|
||||
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
|
||||
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: username.rolename, x: 42 }
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
BSD
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
|
||||
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
|
43
ansible/defaults/main.yml
Normal file
43
ansible/defaults/main.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
paperlessng_version: 0.9.13
|
||||
paperlessng_directory: /opt/paperless-ng
|
||||
paperlessng_consumption_dir: "{{ paperlessng_directory }}/consumption"
|
||||
paperlessng_data_dir: "{{ paperlessng_directory }}/data"
|
||||
paperlessng_media_root: "{{ paperlessng_directory }}/media"
|
||||
paperlessng_static_dir: "{{ paperlessng_directory }}/static"
|
||||
paperlessng_filename_format:
|
||||
paperlessng_virtualenv: "{{ paperlessng_directory }}/.venv"
|
||||
|
||||
paperlessng_ocr_languages:
|
||||
- eng
|
||||
paperlessng_time_zone: Europe/Berlin
|
||||
# see https://ocrmypdf.readthedocs.io/en/latest/api.html#ocrmypdf.ocr
|
||||
paperlessng_ocrmypdf_args:
|
||||
#- "deskew": true # https://github.com/jonaswinkler/paperless-ng/issues/231
|
||||
- "optimize": 1
|
||||
paperlessng_use_jbig2enc: true
|
||||
paperlessng_big2enc_lossy: false
|
||||
paperlessng_tika_enabled: false
|
||||
paperlessng_tika_endpoint: http://localhost:9998
|
||||
paperlessng_tika_gotenberg_endpoint: http://localhost:3000
|
||||
|
||||
paperlessng_superuser_name: paperlessng
|
||||
paperlessng_superuser_email: paperlessng@example.com
|
||||
paperlessng_superuser_password: paperlessng
|
||||
|
||||
paperlessng_system_user: paperlessng
|
||||
paperlessng_system_group: paperlessng
|
||||
|
||||
paperlessng_listen_address: 127.0.0.1
|
||||
paperlessng_listen_port: 8000
|
||||
|
||||
paperlessng_redis_host: localhost
|
||||
paperlessng_redis_port: 6379
|
||||
|
||||
paperlessng_db_type: sqlite # or postgresql
|
||||
# Below entries only apply for paperlessng_db_type=='postgresql'
|
||||
paperlessng_db_host: localhost
|
||||
paperlessng_db_port: 5432
|
||||
paperlessng_db_name: paperlessng
|
||||
paperlessng_db_user: paperlessng
|
||||
paperlessng_db_pass: paperlessng
|
17
ansible/meta/main.yml
Normal file
17
ansible/meta/main.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
dependencies: []
|
||||
|
||||
galaxy_info:
|
||||
author: C0nsultant
|
||||
description: Bare-metal deployment of paperless-ng DMS
|
||||
license: license (GPLv3)
|
||||
min_ansible_version: 2.7
|
||||
|
||||
platforms:
|
||||
- name: Debian
|
||||
versions:
|
||||
- buster
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- focal
|
||||
|
||||
galaxy_tags: [EDMS, django, python, web]
|
7
ansible/molecule/default/converge.yml
Normal file
7
ansible/molecule/default/converge.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: "Include ansible"
|
||||
include_role:
|
||||
name: "ansible"
|
35
ansible/molecule/default/molecule.yml
Normal file
35
ansible/molecule/default/molecule.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: ubuntu_focal
|
||||
image: jrei/systemd-ubuntu:20.04
|
||||
privileged: true
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:ro
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /run
|
||||
- /run/lock
|
||||
override_command: False
|
||||
# ubuntu 18.04 bionic works except that
|
||||
# the default redis configuration expects IPv6 which is not enabled in docker by default
|
||||
# the default Python environment is configured for ASCII instead of UTF-8
|
||||
# ubuntu 16.04 xenial only has Python 3.5 which is EOL and breaks multiple dependencies
|
||||
- name: debian_buster
|
||||
image: jrei/systemd-debian:10
|
||||
privileged: true
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:ro
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /run
|
||||
- /run/lock
|
||||
override_command: False
|
||||
# debian 9 stretch only has Python 3.5 which is EOL and breaks multiple dependencies
|
||||
provisioner:
|
||||
name: ansible
|
||||
verifier:
|
||||
name: ansible
|
14
ansible/molecule/default/verify.yml
Normal file
14
ansible/molecule/default/verify.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
# This is an example playbook to execute Ansible tests.
|
||||
|
||||
- name: Verify
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: check if webserver is up
|
||||
uri:
|
||||
url: http://localhost:8000
|
||||
status_code: [200, 302]
|
||||
return_content: yes
|
||||
register: landingpage
|
||||
failed_when: "'Sign in</button>' not in landingpage.content"
|
428
ansible/tasks/main.yml
Normal file
428
ansible/tasks/main.yml
Normal file
@@ -0,0 +1,428 @@
|
||||
---
|
||||
- name: verify operating system
|
||||
fail:
|
||||
msg: Sorry, only Debian and Ubuntu supported at the moment.
|
||||
when: not(ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu')
|
||||
|
||||
- name: install base dependencies
|
||||
apt:
|
||||
update_cache: yes
|
||||
pkg:
|
||||
# paperless-ng
|
||||
- python3-dev
|
||||
- python3-pip
|
||||
- gettext
|
||||
- fonts-liberation
|
||||
- imagemagick
|
||||
- unpaper
|
||||
- ghostscript
|
||||
- optipng
|
||||
- tesseract-ocr
|
||||
- gnupg
|
||||
- libpoppler-cpp-dev
|
||||
- libmagic-dev
|
||||
- libpq-dev
|
||||
# OCRmyPDF
|
||||
- icc-profiles-free
|
||||
- qpdf
|
||||
- liblept5
|
||||
- libxml2
|
||||
- pngquant
|
||||
- zlib1g
|
||||
# dev
|
||||
- sudo
|
||||
- build-essential
|
||||
- python3-setuptools
|
||||
- python3-wheel
|
||||
- python3-virtualenv
|
||||
|
||||
- name: install ocr languages
|
||||
apt:
|
||||
pkg: "{{ paperlessng_ocr_languages | map('regex_replace', '^(.*)$', 'tesseract-ocr-\\1') | list }}"
|
||||
|
||||
- name: set up notesalexp repository key (for jbig2enc)
|
||||
apt_key:
|
||||
url: https://notesalexp.org/debian/alexp_key.asc
|
||||
state: present
|
||||
when: paperlessng_use_jbig2enc
|
||||
|
||||
- name: set up notesalexp repository (for jbig2enc)
|
||||
apt_repository:
|
||||
repo: "deb https://notesalexp.org/debian/{{ ansible_distribution_release }}/ {{ ansible_distribution_release }} main"
|
||||
state: present
|
||||
when: paperlessng_use_jbig2enc
|
||||
|
||||
- name: set up notesalexp repository pinning
|
||||
copy:
|
||||
content: |
|
||||
Package: *
|
||||
Pin: release o=notesalexp.org
|
||||
Pin-Priority: 1
|
||||
|
||||
Package: jbig2enc
|
||||
Pin: release o=notesalexp.org
|
||||
Pin-Priority: 500
|
||||
dest: /etc/apt/preferences.d/notesalexp
|
||||
when: paperlessng_use_jbig2enc
|
||||
|
||||
- name: install jbig2enc
|
||||
apt:
|
||||
pkg: jbig2enc
|
||||
update_cache: yes
|
||||
when: paperlessng_use_jbig2enc
|
||||
|
||||
- name: install redis
|
||||
apt:
|
||||
pkg: redis-server
|
||||
when: paperlessng_redis_host == 'localhost' or paperlessng_redis_host == '127.0.0.1'
|
||||
|
||||
- name: enable redis
|
||||
systemd:
|
||||
name: redis-server
|
||||
enabled: yes
|
||||
masked: no
|
||||
state: started
|
||||
when: paperlessng_redis_host == 'localhost' or paperlessng_redis_host == '127.0.0.1'
|
||||
|
||||
- name: create paperless system group
|
||||
group:
|
||||
name: "{{ paperlessng_system_group }}"
|
||||
|
||||
- name: create paperless system user
|
||||
user:
|
||||
name: "{{ paperlessng_system_user }}"
|
||||
groups:
|
||||
- "{{ paperlessng_system_group }}"
|
||||
shell: /usr/sbin/nologin
|
||||
# GNUPG_HOME required due to paperless db.py
|
||||
create_home: yes
|
||||
|
||||
- name: check for paperless-ng installation
|
||||
command:
|
||||
cmd: 'grep -Po "(?<=Paperless-ng )\d+\.\d+\.\d+" {{ paperlessng_directory }}/docs/changelog.html'
|
||||
changed_when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
failed_when: false
|
||||
ignore_errors: yes
|
||||
register: paperlessng_current_version
|
||||
|
||||
- name: backup current paperless-ng installation
|
||||
copy:
|
||||
src: "{{ paperlessng_directory }}"
|
||||
remote_src: yes
|
||||
dest: "{{ paperlessng_directory }}-{{ ansible_date_time.iso8601 }}/"
|
||||
when: '"No such file or directory" not in paperlessng_current_version.stderr and paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
|
||||
- name: create temporary directory
|
||||
tempfile:
|
||||
state: directory
|
||||
register: tempdir
|
||||
when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
|
||||
- name: extract paperless-ng
|
||||
unarchive:
|
||||
src: "https://github.com/jonaswinkler/paperless-ng/releases/download/ng-{{ paperlessng_version }}/paperless-ng-{{ paperlessng_version }}.tar.xz"
|
||||
remote_src: yes
|
||||
dest: "{{ tempdir.path }}"
|
||||
when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
|
||||
- name: change owner and permissions of paperless-ng
|
||||
command:
|
||||
cmd: "{{ item }}"
|
||||
warn: false
|
||||
with_items:
|
||||
- "chown -R {{ paperlessng_system_user }}:{{ paperlessng_system_group }} {{ tempdir.path }}"
|
||||
- "find {{ tempdir.path }} -type d -exec chmod 0750 {} ;"
|
||||
- "find {{ tempdir.path }} -type f -exec chmod 0640 {} ;"
|
||||
when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
|
||||
- name: move paperless-ng
|
||||
command:
|
||||
cmd: "cp -a {{ tempdir.path }}/paperless-ng/ {{ paperlessng_directory }}"
|
||||
when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
|
||||
- name: remove temporary directory
|
||||
file:
|
||||
path: "{{ tempdir.path }}"
|
||||
state: absent
|
||||
when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
|
||||
|
||||
- name: create paperless-ng directories and set permissions
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ paperlessng_system_user }}"
|
||||
group: "{{ paperlessng_system_group }}"
|
||||
mode: "750"
|
||||
with_items:
|
||||
- "{{ paperlessng_directory }}" # ansible `copy:` does not set correct permissions on `dest:` for recursive copies
|
||||
- "{{ paperlessng_consumption_dir }}"
|
||||
- "{{ paperlessng_data_dir }}"
|
||||
- "{{ paperlessng_media_root }}"
|
||||
- "{{ paperlessng_static_dir }}"
|
||||
|
||||
- name: rename initial config
|
||||
command:
|
||||
cmd: "mv {{ paperlessng_directory }}/paperless.conf {{ paperlessng_directory }}/paperless.conf.template"
|
||||
removes: "{{ paperlessng_directory }}/paperless.conf"
|
||||
|
||||
- name: configure paperless-ng
|
||||
lineinfile:
|
||||
path: "{{ paperlessng_directory }}/paperless.conf.template"
|
||||
regexp: "^#?{{ item.regexp }}="
|
||||
line: "{{ item.line }}"
|
||||
with_items:
|
||||
- regexp: PAPERLESS_REDIS
|
||||
line: "PAPERLESS_REDIS=redis://{{ paperlessng_redis_host }}:{{ paperlessng_redis_port }}"
|
||||
- regexp: PAPERLESS_CONSUMPTION_DIR
|
||||
line: "PAPERLESS_CONSUMPTION_DIR={{ paperlessng_consumption_dir }}"
|
||||
- regexp: PAPERLESS_DATA_DIR
|
||||
line: "PAPERLESS_DATA_DIR={{ paperlessng_data_dir }}"
|
||||
- regexp: PAPERLESS_MEDIA_ROOT
|
||||
line: "PAPERLESS_MEDIA_ROOT={{ paperlessng_media_root }}"
|
||||
- regexp: PAPERLESS_STATICDIR
|
||||
line: "PAPERLESS_STATICDIR={{ paperlessng_static_dir }}"
|
||||
- regexp: PAPERLESS_FILENAME_FORMAT
|
||||
line: "PAPERLESS_FILENAME_FORMAT={{ paperlessng_filename_format }}"
|
||||
- regexp: PAPERLESS_OCR_LANGUAGE
|
||||
line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') }}"
|
||||
- regexp: PAPERLESS_OCR_USER_ARGS
|
||||
line: "PAPERLESS_OCR_USER_ARGS={{ paperlessng_ocrmypdf_args | combine({'jbig2_lossy': true} if paperlessng_big2enc_lossy else {}) | to_json }}"
|
||||
- regexp: PAPERLESS_TIME_ZONE
|
||||
line: "PAPERLESS_TIME_ZONE={{ paperlessng_time_zone }}"
|
||||
- regexp: PAPERLESS_TIKA_ENABLED
|
||||
line: "PAPERLESS_TIKA_ENABLED={{ paperlessng_tika_enabled }}"
|
||||
no_log: yes
|
||||
|
||||
- name: configure paperless-ng [tika]
|
||||
lineinfile:
|
||||
path: "{{ paperlessng_directory }}/paperless.conf.template"
|
||||
regexp: "^#?{{ item.regexp }}="
|
||||
line: "'{{ item.line }}' if paperlessng_tika_enabled else '#{{ item.line }}'"
|
||||
with_items:
|
||||
- regexp: PAPERLESS_TIKA_ENDPOINT
|
||||
line: "PAPERLESS_TIKA_ENDPOINT={{ paperlessng_tika_endpoint }}"
|
||||
- regexp: PAPERLESS_TIKA_GOTENBERG_ENDPOINT
|
||||
line: "PAPERLESS_TIKA_GOTENBERG_ENDPOINT={{ paperlessng_tika_endpoint }}"
|
||||
|
||||
- name: configure paperless-ng database [sqlite]
|
||||
lineinfile:
|
||||
path: "{{ paperlessng_directory }}/paperless.conf.template"
|
||||
regexp: "^#?PAPERLESS_DBHOST=(.*)$"
|
||||
line: '#PAPERLESS_DBHOST=\1'
|
||||
backrefs: yes
|
||||
when: paperlessng_db_type == 'sqlite'
|
||||
|
||||
- name: configure paperless-ng database [postgresql]
|
||||
lineinfile:
|
||||
path: "{{ paperlessng_directory }}/paperless.conf.template"
|
||||
regexp: "^#?{{ item.regexp }}="
|
||||
line: "{{ item.line }}"
|
||||
with_items:
|
||||
- regexp: PAPERLESS_DBHOST
|
||||
line: "PAPERLESS_DBHOST={{ paperlessng_db_host }}"
|
||||
- regexp: PAPERLESS_DBPORT
|
||||
line: "PAPERLESS_DBPORT={{ paperlessng_db_port }}"
|
||||
- regexp: PAPERLESS_DBNAME
|
||||
line: "PAPERLESS_DBNAME={{ paperlessng_db_name }}"
|
||||
- regexp: PAPERLESS_DBUSER
|
||||
line: "PAPERLESS_DBUSER={{ paperlessng_db_user }}"
|
||||
- regexp: PAPERLESS_DBPASS
|
||||
line: "PAPERLESS_DBPASS={{ paperlessng_db_pass }}"
|
||||
when: paperlessng_db_type == 'postgresql'
|
||||
no_log: yes
|
||||
|
||||
- name: deploy paperless-ng configuration
|
||||
copy:
|
||||
src: "{{ paperlessng_directory }}/paperless.conf.template"
|
||||
remote_src: yes
|
||||
dest: /etc/paperless.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
register: configuration
|
||||
|
||||
- name: create paperlessng venv
|
||||
become: yes
|
||||
become_user: "{{ paperlessng_system_user }}"
|
||||
command:
|
||||
cmd: "python3 -m virtualenv {{ paperlessng_virtualenv }} -p /usr/bin/python3"
|
||||
creates: "{{ paperlessng_virtualenv }}"
|
||||
register: venv
|
||||
|
||||
- name: install paperlessng requirements
|
||||
become: yes
|
||||
become_user: "{{ paperlessng_system_user }}"
|
||||
pip:
|
||||
requirements: "{{ paperlessng_directory }}/requirements.txt"
|
||||
executable: "{{ paperlessng_virtualenv }}/bin/pip3"
|
||||
extra_args: --upgrade
|
||||
when: paperlessng_current_version.stdout != paperlessng_version | string
|
||||
|
||||
- name: collect static files
|
||||
become: yes
|
||||
become_user: "{{ paperlessng_system_user }}"
|
||||
command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py collectstatic --no-input"
|
||||
when: paperlessng_current_version.stdout != paperlessng_version | string
|
||||
register: static_files
|
||||
changed_when: static_files.stdout is not match("0 static files copied .*")
|
||||
|
||||
- name: create database schema
|
||||
become: yes
|
||||
become_user: "{{ paperlessng_system_user }}"
|
||||
command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py migrate"
|
||||
when: paperlessng_current_version.stdout != paperlessng_version | string
|
||||
register: database_schema
|
||||
changed_when: '"No migrations to apply." not in database_schema.stdout'
|
||||
|
||||
- name: compile translations
|
||||
become: yes
|
||||
become_user: "{{ paperlessng_system_user }}"
|
||||
command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py compilemessages"
|
||||
when: paperlessng_current_version.stdout != paperlessng_version | string
|
||||
|
||||
- name: configure paperless superuser
|
||||
become: yes
|
||||
become_user: "{{ paperlessng_system_user }}"
|
||||
# "manage.py createsuperuser" only works on interactive TTYs
|
||||
vars:
|
||||
creation_script: |
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import get_hasher
|
||||
|
||||
if User.objects.filter(username='{{ paperlessng_superuser_name }}').exists():
|
||||
user = User.objects.get(username='{{ paperlessng_superuser_name }}')
|
||||
old = user.__dict__.copy()
|
||||
|
||||
user.is_superuser = True
|
||||
user.email = '{{ paperlessng_superuser_email }}'
|
||||
user.set_password('{{ paperlessng_superuser_password }}')
|
||||
user.save()
|
||||
new = user.__dict__
|
||||
|
||||
algorithm, iterations, old_salt, old_hash = old['password'].split('$')
|
||||
new_password_old_salt = get_hasher(algorithm).encode(password='{{ paperlessng_superuser_password }}', salt=old_salt, iterations=int(iterations))
|
||||
_, _, _, new_hash = new_password_old_salt.split('$')
|
||||
if not (old_hash == new_hash and old['is_superuser'] == new['is_superuser'] and old['email'] == new['email']):
|
||||
print('changed')
|
||||
else:
|
||||
User.objects.create_superuser('{{ paperlessng_superuser_name }}', '{{ paperlessng_superuser_email }}', '{{ paperlessng_superuser_password }}')
|
||||
print('changed')
|
||||
command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py shell -c \"{{ creation_script }}\""
|
||||
register: superuser
|
||||
changed_when: superuser.stdout == 'changed'
|
||||
no_log: yes
|
||||
|
||||
- name: set ownership and permissions on paperlessng venv
|
||||
file:
|
||||
path: "{{ paperlessng_virtualenv }}"
|
||||
state: directory
|
||||
recurse: yes
|
||||
owner: "{{ paperlessng_system_user }}"
|
||||
group: "{{ paperlessng_system_group }}"
|
||||
mode: g-w,o-rwx
|
||||
when: venv.changed or paperlessng_current_version.stdout != paperlessng_version | string
|
||||
|
||||
- name: configure ghostscript for PDF
|
||||
lineinfile:
|
||||
path: /etc/ImageMagick-6/policy.xml
|
||||
regexp: '(\s+)<policy domain="coder" rights=".*" pattern="PDF" />'
|
||||
line: '\1<policy domain="coder" rights="read|write" pattern="PDF" />'
|
||||
backrefs: yes
|
||||
|
||||
- name: configure systemd services
|
||||
ini_file:
|
||||
path: "{{ paperlessng_directory }}/scripts/{{ item[0] }}"
|
||||
section: "Service"
|
||||
option: "{{ item[1].option }}"
|
||||
value: "{{ item[1].value }}"
|
||||
with_nested:
|
||||
- [
|
||||
paperless-consumer.service,
|
||||
paperless-scheduler.service,
|
||||
paperless-webserver.service,
|
||||
]
|
||||
- [
|
||||
# https://www.freedesktop.org/software/systemd/man/systemd.exec.html
|
||||
{
|
||||
option: "User",
|
||||
value: "{{ paperlessng_system_user }}",
|
||||
},
|
||||
{
|
||||
option: "Group",
|
||||
value: "{{ paperlessng_system_group }}",
|
||||
},
|
||||
{
|
||||
option: "WorkingDirectory",
|
||||
value: "{{ paperlessng_directory }}/src",
|
||||
},
|
||||
{
|
||||
option: "ProtectSystem",
|
||||
value: "full",
|
||||
},
|
||||
{
|
||||
option: "NoNewPrivileges",
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
option: "PrivateUsers",
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
option: "PrivateDevices",
|
||||
value: "true",
|
||||
}
|
||||
]
|
||||
|
||||
- name: configure paperless-consumer service
|
||||
ini_file:
|
||||
path: "{{ paperlessng_directory }}/scripts/paperless-consumer.service"
|
||||
section: "Service"
|
||||
option: "ExecStart"
|
||||
value: "{{ paperlessng_virtualenv }}/bin/python3 manage.py document_consumer"
|
||||
|
||||
- name: configure paperless-scheduler service
|
||||
ini_file:
|
||||
path: "{{ paperlessng_directory }}/scripts/paperless-scheduler.service"
|
||||
section: "Service"
|
||||
option: "ExecStart"
|
||||
value: "{{ paperlessng_virtualenv }}/bin/python3 manage.py qcluster"
|
||||
|
||||
- name: configure paperless-webserver service
|
||||
ini_file:
|
||||
path: "{{ paperlessng_directory }}/scripts/paperless-webserver.service"
|
||||
section: "Service"
|
||||
option: "ExecStart"
|
||||
value: "{{ paperlessng_virtualenv }}/bin/gunicorn paperless.wsgi -w 2 -b {{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}"
|
||||
|
||||
- name: copy systemd services
|
||||
copy:
|
||||
src: "{{ paperlessng_directory }}/scripts/{{ item }}"
|
||||
remote_src: yes
|
||||
dest: "/etc/systemd/system/{{ item }}"
|
||||
with_items:
|
||||
- paperless-consumer.service
|
||||
- paperless-scheduler.service
|
||||
- paperless-webserver.service
|
||||
register: paperless_services
|
||||
|
||||
- name: reload systemd daemon
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
with_items:
|
||||
- paperless-consumer
|
||||
- paperless-scheduler
|
||||
- paperless-webserver
|
||||
when: paperless_services.changed or configuration.changed
|
||||
|
||||
- name: enable paperlessng services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: yes
|
||||
masked: no
|
||||
state: started
|
||||
with_items:
|
||||
- paperless-consumer
|
||||
- paperless-scheduler
|
||||
- paperless-webserver
|
7
compile-frontend.sh
Executable file
7
compile-frontend.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd src-ui
|
||||
npm install
|
||||
./node_modules/.bin/ng build --prod
|
1
docker/compose/.env
Normal file
1
docker/compose/.env
Normal file
@@ -0,0 +1 @@
|
||||
COMPOSE_PROJECT_NAME=paperless
|
@@ -7,7 +7,7 @@
|
||||
# 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
|
||||
# default language used when guessing the language from the OCR output.
|
||||
# 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
|
90
docker/compose/docker-compose.postgres-tika.yml
Normal file
90
docker/compose/docker-compose.postgres-tika.yml
Normal file
@@ -0,0 +1,90 @@
|
||||
# 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 8000.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
# - Apache Tika and Gotenberg servers are started with paperless and paperless
|
||||
# is configured to use these services. These provide support for consuming
|
||||
# Office documents (Word, Excel, Power Point and their LibreOffice counter-
|
||||
# parts.
|
||||
#
|
||||
# To install and update paperless with this file, do the following:
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# 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
|
||||
- gotenberg
|
||||
- tika
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: thecodingmachine/gotenberg
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DISABLE_GOOGLE_CHROME: 1
|
||||
|
||||
tika:
|
||||
image: apache/tika
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
pgdata:
|
72
docker/compose/docker-compose.postgres.yml
Normal file
72
docker/compose/docker-compose.postgres.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
# 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 8000.
|
||||
#
|
||||
# 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:
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# 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:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
pgdata:
|
78
docker/compose/docker-compose.sqlite-tika.yml
Normal file
78
docker/compose/docker-compose.sqlite-tika.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
# 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 8000.
|
||||
#
|
||||
# SQLite is used as the database. The SQLite file is stored in the data volume.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Apache Tika and Gotenberg servers are started with paperless and paperless
|
||||
# is configured to use these services. These provide support for consuming
|
||||
# Office documents (Word, Excel, Power Point and their LibreOffice counter-
|
||||
# parts.
|
||||
#
|
||||
# To install and update paperless with this file, do the following:
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: redis:6.0
|
||||
restart: unless-stopped
|
||||
|
||||
webserver:
|
||||
image: jonaswinkler/paperless-ng:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- broker
|
||||
- gotenberg
|
||||
- tika
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: thecodingmachine/gotenberg
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DISABLE_GOOGLE_CHROME: 1
|
||||
|
||||
tika:
|
||||
image: apache/tika
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
56
docker/compose/docker-compose.sqlite.yml
Normal file
56
docker/compose/docker-compose.sqlite.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
# 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 8000.
|
||||
#
|
||||
# SQLite is used as the database. The SQLite file is stored in the data volume.
|
||||
#
|
||||
# To install and update paperless with this file, do the following:
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: redis:6.0
|
||||
restart: unless-stopped
|
||||
|
||||
webserver:
|
||||
image: jonaswinkler/paperless-ng:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- broker
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
@@ -62,6 +62,7 @@ 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
|
||||
|
||||
@@ -85,6 +86,8 @@ initialize() {
|
||||
}
|
||||
|
||||
install_languages() {
|
||||
echo "Installing languages..."
|
||||
|
||||
local langs="$1"
|
||||
read -ra langs <<<"$langs"
|
||||
|
||||
@@ -119,6 +122,8 @@ install_languages() {
|
||||
done
|
||||
}
|
||||
|
||||
echo "Paperless-ng docker container starting..."
|
||||
|
||||
# Install additional languages if specified
|
||||
if [[ ! -z "$PAPERLESS_OCR_LANGUAGES" ]]; then
|
||||
install_languages "$PAPERLESS_OCR_LANGUAGES"
|
||||
@@ -127,8 +132,10 @@ fi
|
||||
initialize
|
||||
|
||||
if [[ "$1" != "/"* ]]; then
|
||||
echo Executing management command "$@"
|
||||
exec sudo -HEu paperless python3 manage.py "$@"
|
||||
else
|
||||
echo Executing "$@"
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
bind = '127.0.0.1:8000'
|
||||
bind = '0.0.0.0:8000'
|
||||
backlog = 2048
|
||||
workers = 3
|
||||
worker_class = 'sync'
|
||||
|
@@ -1,44 +0,0 @@
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: redis:6.0
|
||||
restart: always
|
||||
|
||||
db:
|
||||
image: postgres:13
|
||||
restart: always
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_PASSWORD: paperless
|
||||
|
||||
webserver:
|
||||
image: jonaswinkler/paperless-ng:0.9.6
|
||||
restart: always
|
||||
depends_on:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
pgdata:
|
@@ -1,31 +0,0 @@
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: redis:6.0
|
||||
restart: always
|
||||
|
||||
webserver:
|
||||
image: jonaswinkler/paperless-ng:0.9.6
|
||||
restart: always
|
||||
depends_on:
|
||||
- broker
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
@@ -1,44 +0,0 @@
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: redis:6.0
|
||||
restart: always
|
||||
|
||||
db:
|
||||
image: postgres:13
|
||||
restart: always
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_PASSWORD: paperless
|
||||
|
||||
webserver:
|
||||
build: .
|
||||
restart: always
|
||||
depends_on:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
pgdata:
|
@@ -1,31 +0,0 @@
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: redis:6.0
|
||||
restart: always
|
||||
|
||||
webserver:
|
||||
build: .
|
||||
restart: always
|
||||
depends_on:
|
||||
- broker
|
||||
ports:
|
||||
- 8000: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
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
@@ -8,7 +8,7 @@ loglevel=info ; log level; default info; others: debug,warn,trace
|
||||
user=root
|
||||
|
||||
[program:gunicorn]
|
||||
command=gunicorn -c /usr/src/paperless/gunicorn.conf.py -b 0.0.0.0:8000 paperless.wsgi
|
||||
command=gunicorn -c /usr/src/paperless/gunicorn.conf.py paperless.wsgi
|
||||
user=paperless
|
||||
|
||||
stdout_logfile=/dev/stdout
|
||||
|
@@ -48,16 +48,13 @@ Options available to bare-metal and non-docker installations:
|
||||
Restoring
|
||||
=========
|
||||
|
||||
|
||||
|
||||
|
||||
.. _administration-updating:
|
||||
|
||||
Updating paperless
|
||||
Updating Paperless
|
||||
##################
|
||||
|
||||
If a new release of paperless-ng is available, upgrading depends on how you
|
||||
installed paperless-ng in the first place. The releases are available at
|
||||
installed paperless-ng in the first place. The releases are available at the
|
||||
`release page <https://github.com/jonaswinkler/paperless-ng/releases>`_.
|
||||
|
||||
First of all, ensure that paperless is stopped.
|
||||
@@ -69,50 +66,28 @@ First of all, ensure that paperless is stopped.
|
||||
|
||||
After that, :ref:`make a backup <administration-backup>`.
|
||||
|
||||
A. If you used the dockerfiles archive, simply download the files of the new release,
|
||||
adjust the settings in the files (i.e., the path to your consumption directory),
|
||||
and replace your existing docker-compose files. Then start paperless as usual,
|
||||
which will pull the new image, and update your database, if necessary:
|
||||
A. If you pull the image from the docker hub, all you need to do is:
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ cd /path/to/paperless
|
||||
$ docker-compose pull
|
||||
$ docker-compose up
|
||||
|
||||
If you see everything working, you can start paperless-ng with "-d" to have it
|
||||
run in the background.
|
||||
|
||||
.. hint::
|
||||
|
||||
The released docker-compose files specify exact versions to be pulled from the hub.
|
||||
This is to ensure that if the docker-compose files should change at some point
|
||||
(i.e., services updates/configured differently), you wont run into trouble due to
|
||||
docker pulling the ``latest`` image and running it in an older environment.
|
||||
|
||||
The docker-compose files refer to the ``latest`` version, which is always the latest
|
||||
stable release.
|
||||
|
||||
B. If you built the image yourself, grab the new archive and replace your current
|
||||
paperless folder with the new contents.
|
||||
|
||||
After that, make the necessary adjustments to the docker-compose.yml (i.e.,
|
||||
adjust your consumption directory).
|
||||
|
||||
Build and start the new image with:
|
||||
B. If you built the image yourself, do the following:
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ cd /path/to/paperless
|
||||
$ git pull
|
||||
$ ./compile-frontend.sh
|
||||
$ docker-compose build
|
||||
$ docker-compose up
|
||||
|
||||
If you see everything working, you can start paperless-ng with "-d" to have it
|
||||
run in the background.
|
||||
|
||||
.. hint::
|
||||
|
||||
You can usually keep your ``docker-compose.env`` file, since this file will
|
||||
never include mandatory configuration options. However, it is worth checking
|
||||
out the new version of this file, since it might have new recommendations
|
||||
on what to configure.
|
||||
|
||||
Running `docker-compose up` will also apply any new database migrations.
|
||||
If you see everything working, press CTRL+C once to gracefully stop paperless.
|
||||
Then you can start paperless-ng with ``-d`` to have it run in the background.
|
||||
|
||||
Updating paperless without docker
|
||||
=================================
|
||||
@@ -135,20 +110,19 @@ After grabbing the new release and unpacking the contents, do the following:
|
||||
This creates a new virtual environment (or uses your existing environment)
|
||||
and installs all dependencies into it.
|
||||
|
||||
3. Collect static files.
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ cd src
|
||||
$ pipenv run python3 manage.py collectstatic --clear
|
||||
You can also use the included ``requirements.txt`` file instead and create the virtual
|
||||
environment yourself. This file includes exactly the same dependencies.
|
||||
|
||||
4. Migrate the database.
|
||||
3. Migrate the database.
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ cd src
|
||||
$ pipenv run python3 manage.py migrate
|
||||
|
||||
|
||||
This might not actually do anything. Not every new paperless version comes with new
|
||||
database migrations.
|
||||
|
||||
|
||||
Management utilities
|
||||
####################
|
||||
|
@@ -5,85 +5,6 @@ Advanced topics
|
||||
Paperless offers a couple features that automate certain tasks and make your life
|
||||
easier.
|
||||
|
||||
Guesswork
|
||||
#########
|
||||
|
||||
|
||||
Any document you put into the consumption directory will be consumed, but if
|
||||
you name the file right, it'll automatically set some values in the database
|
||||
for you. This is is the logic the consumer follows:
|
||||
|
||||
1. Try to find the correspondent, title, and tags in the file name following
|
||||
the pattern: ``Date - Correspondent - Title - tag,tag,tag.pdf``. Note that
|
||||
the format of the date is **rigidly defined** as ``YYYYMMDDHHMMSSZ`` or
|
||||
``YYYYMMDDZ``. The ``Z`` refers "Zulu time" AKA "UTC".
|
||||
The tags are optional, so the format ``Date - Correspondent - Title.pdf``
|
||||
works as well.
|
||||
2. If that doesn't work, we skip the date and try this pattern:
|
||||
``Correspondent - Title - tag,tag,tag.pdf``.
|
||||
3. If that doesn't work, we try to find the correspondent and title in the file
|
||||
name following the pattern: ``Correspondent - Title.pdf``.
|
||||
4. If that doesn't work, just assume that the name of the file is the title.
|
||||
|
||||
So given the above, the following examples would work as you'd expect:
|
||||
|
||||
* ``20150314000700Z - Some Company Name - Invoice 2016-01-01 - money,invoices.pdf``
|
||||
* ``20150314Z - Some Company Name - Invoice 2016-01-01 - money,invoices.pdf``
|
||||
* ``Some Company Name - Invoice 2016-01-01 - money,invoices.pdf``
|
||||
* ``Another Company - Letter of Reference.jpg``
|
||||
* ``Dad's Recipe for Pancakes.png``
|
||||
|
||||
These however wouldn't work:
|
||||
|
||||
* ``2015-03-14 00:07:00 UTC - Some Company Name, Invoice 2016-01-01, money, invoices.pdf``
|
||||
* ``2015-03-14 - Some Company Name, Invoice 2016-01-01, money, invoices.pdf``
|
||||
* ``Some Company Name, Invoice 2016-01-01, money, invoices.pdf``
|
||||
* ``Another Company- Letter of Reference.jpg``
|
||||
|
||||
Do I have to be so strict about naming?
|
||||
=======================================
|
||||
|
||||
Rather than using the strict document naming rules, one can also set the option
|
||||
``PAPERLESS_FILENAME_DATE_ORDER`` in ``paperless.conf`` to any date order
|
||||
that is accepted by dateparser_. Doing so will cause ``paperless`` to default
|
||||
to any date format that is found in the title, instead of a date pulled from
|
||||
the document's text, without requiring the strict formatting of the document
|
||||
filename as described above.
|
||||
|
||||
.. _dateparser: https://github.com/scrapinghub/dateparser/blob/v0.7.0/docs/usage.rst#settings
|
||||
|
||||
.. _advanced-transforming_filenames:
|
||||
|
||||
Transforming filenames for parsing
|
||||
==================================
|
||||
|
||||
Some devices can't produce filenames that can be parsed by the default
|
||||
parser. By configuring the option ``PAPERLESS_FILENAME_PARSE_TRANSFORMS`` in
|
||||
``paperless.conf`` one can add transformations that are applied to the filename
|
||||
before it's parsed.
|
||||
|
||||
The option contains a list of dictionaries of regular expressions (key:
|
||||
``pattern``) and replacements (key: ``repl``) in JSON format, which are
|
||||
applied in order by passing them to ``re.subn``. Transformation stops
|
||||
after the first match, so at most one transformation is applied. The general
|
||||
syntax is
|
||||
|
||||
.. code:: python
|
||||
|
||||
[{"pattern":"pattern1", "repl":"repl1"}, {"pattern":"pattern2", "repl":"repl2"}, ..., {"pattern":"patternN", "repl":"replN"}]
|
||||
|
||||
The example below is for a Brother ADS-2400N, a scanner that allows
|
||||
different names to different hardware buttons (useful for handling
|
||||
multiple entities in one instance), but insists on adding ``_<count>``
|
||||
to the filename.
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Brother profile configuration, support "Name_Date_Count" (the default
|
||||
# setting) and "Name_Count" (use "Name" as tag and "Count" as title).
|
||||
PAPERLESS_FILENAME_PARSE_TRANSFORMS=[{"pattern":"^([a-z]+)_(\\d{8})_(\\d{6})_([0-9]+)\\.", "repl":"\\2\\3Z - \\4 - \\1."}, {"pattern":"^([a-z]+)_([0-9]+)\\.", "repl":" - \\2 - \\1."}]
|
||||
|
||||
|
||||
.. _advanced-matching:
|
||||
|
||||
Matching tags, correspondents and document types
|
||||
@@ -263,10 +184,10 @@ using the identifier which it has assigned to each document. You will end up get
|
||||
files like ``0000123.pdf`` in your media directory. This isn't necessarily a bad
|
||||
thing, because you normally don't have to access these files manually. However, if
|
||||
you wish to name your files differently, you can do that by adjusting the
|
||||
``PAPERLESS_FILENAME_FORMAT`` settings variable.
|
||||
``PAPERLESS_FILENAME_FORMAT`` configuration option.
|
||||
|
||||
This variable allows you to configure the filename (folders are allowed!) using
|
||||
placeholders. For example, setting
|
||||
This variable allows you to configure the filename (folders are allowed) using
|
||||
placeholders. For example, configuring this to
|
||||
|
||||
.. code:: bash
|
||||
|
||||
@@ -277,17 +198,16 @@ will create a directory structure as follows:
|
||||
.. code::
|
||||
|
||||
2019/
|
||||
my_bank/
|
||||
statement-january-0000001.pdf
|
||||
statement-february-0000002.pdf
|
||||
My bank/
|
||||
Statement January.pdf
|
||||
Statement February.pdf
|
||||
2020/
|
||||
my_bank/
|
||||
statement-january-0000003.pdf
|
||||
shoe_store/
|
||||
my_new_shoes-0000004.pdf
|
||||
|
||||
Paperless appends the unique identifier of each document to the filename. This
|
||||
avoids filename clashes.
|
||||
My bank/
|
||||
Statement January.pdf
|
||||
Letter.pdf
|
||||
Letter_01.pdf
|
||||
Shoe store/
|
||||
My new shoes.pdf
|
||||
|
||||
.. danger::
|
||||
|
||||
@@ -299,6 +219,7 @@ Paperless provides the following placeholders withing filenames:
|
||||
|
||||
* ``{correspondent}``: The name of the correspondent, or "none".
|
||||
* ``{document_type}``: The name of the document type, or "none".
|
||||
* ``{tag_list}``: A comma separated list of all tags assigned to the document.
|
||||
* ``{title}``: The title of the document.
|
||||
* ``{created}``: The full date and time the document was created.
|
||||
* ``{created_year}``: Year created only.
|
||||
@@ -309,8 +230,14 @@ Paperless provides the following placeholders withing filenames:
|
||||
* ``{added_month}``: Month added only (number 1-12).
|
||||
* ``{added_day}``: Day added only (number 1-31).
|
||||
|
||||
Paperless will convert all values for the placeholders into values which are safe
|
||||
for use in filenames.
|
||||
|
||||
Paperless will try to conserve the information from your database as much as possible.
|
||||
However, some characters that you can use in document titles and correspondent names (such
|
||||
as ``: \ /`` and a couple more) are not allowed in filenames and will be replaced with dashes.
|
||||
|
||||
If paperless detects that two documents share the same filename, paperless will automatically
|
||||
append ``_01``, ``_02``, etc to the filename. This happens if all the placeholders in a filename
|
||||
evaluate to the same value.
|
||||
|
||||
.. hint::
|
||||
|
||||
|
15
docs/api.rst
15
docs/api.rst
@@ -221,21 +221,16 @@ Each fragment contains a list of strings, and some of them are marked as a highl
|
||||
|
||||
[
|
||||
[
|
||||
{"text": "This is a sample text with a "},
|
||||
{"text": "highlighted", "term": 0},
|
||||
{"text": " word."}
|
||||
{"text": "This is a sample text with a ", "highlight": false},
|
||||
{"text": "highlighted", "highlight": true},
|
||||
{"text": " word.", "highlight": false}
|
||||
],
|
||||
[
|
||||
{"text": "Another", "term": 1},
|
||||
{"text": " fragment with a highlight."}
|
||||
{"text": "Another", "highlight": true},
|
||||
{"text": " fragment with a highlight.", "highlight": false}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
When ``term`` is present within a string, the word within ``text`` should be highlighted.
|
||||
The term index groups multiple matches together and words with the same index
|
||||
should get identical highlighting.
|
||||
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. ...
|
||||
|
@@ -5,6 +5,226 @@
|
||||
Changelog
|
||||
*********
|
||||
|
||||
paperless-ng 0.9.14
|
||||
###################
|
||||
|
||||
Starting with this version, releases are getting built automatically. This release also comes with changes on how to install and
|
||||
update paperless.
|
||||
|
||||
* Paperless now uses GitHub Actions to make releases and build docker images.
|
||||
|
||||
* Docker images are available for amd64, armhf, and aarch64.
|
||||
* When you pull an image from Docker Hub, Docker will automatically select the correct image for you.
|
||||
|
||||
* Changes to docker installations and updates
|
||||
|
||||
* The ``-dockerfiles.tar.xz`` release archive is gone. Instead, simply grab the docker files from ``/docker/compose`` in the repository
|
||||
if you wish to install paperless by pulling from the hub.
|
||||
* The docker compose files in ``/docker/compose`` were changed to always use the ``latest`` version automatically. In order to do further
|
||||
updates, simply do a ``docker-compose pull``. The documentation has been updated.
|
||||
* The docker compose files were changed to restart paperless on system boot only if it was running before shutdown.
|
||||
* Documentation of the docker-compose files about what they do.
|
||||
|
||||
* Changes to bare metal installations and updates
|
||||
|
||||
* The release archive is built exactly like before. However, the release now comes with already compiled translation messages and
|
||||
collected static files. Therefore, the update steps ``compilemessages`` and ``collectstatic`` are now obsolete.
|
||||
|
||||
* Other changes
|
||||
|
||||
* A new configuration option ``PAPERLESS_IGNORE_DATES`` was added by `jayme-github`_. This can be used to instruct paperless to ignore
|
||||
certain dates (such as your date of birth) when guessing the date from the document content. This was actually introduced in 0.9.12,
|
||||
I just forgot to mention it in the changelog.
|
||||
* The filter drop downs now display selected entries on top of all other entries.
|
||||
* The PostgreSQL client now supports setting an explicit ``sslmode`` to force encryption of the connection to PostgreSQL.
|
||||
* The docker images now come with ``jbig2enc``, which is a lossless image encoder for PDF documents and decreases the size of certain
|
||||
PDF/A documents.
|
||||
* When using any of the manual matching algorithms, paperless now logs messages about when and why these matching algorithms matched.
|
||||
* The default settings for parallelization in paperless were adjusted to always leave one CPU core free.
|
||||
* Added an option to the frontend to choose which method to use for displaying PDF documents.
|
||||
|
||||
* Fixes
|
||||
|
||||
* An issue with the tika parser not picking up files from the consumption directory was fixed.
|
||||
* A couple changes to the dark mode and fixes to several other layout issues.
|
||||
* An issue with the drop downs for correspondents, tags and types not properly supporting filtering with special characters was fixed.
|
||||
* Fixed an issue with filenames of downloaded files: Dates where off by one day due to timezone issues.
|
||||
* Searching will continue to work even when the index returns non-existing documents. This resulted in "Document does not exist" errors
|
||||
before. Instead, a warning is logged, indicating the issue.
|
||||
* An issue with the consumer crashing when invalid regular expression were used was fixed.
|
||||
|
||||
paperless-ng 0.9.13
|
||||
###################
|
||||
|
||||
* Fixed an issue with Paperless not starting due to the new Tika integration when ``USERMAP_UID`` and ``USERMAP_GID`` was used
|
||||
in the ``docker-compose.env`` file.
|
||||
|
||||
paperless-ng 0.9.12
|
||||
###################
|
||||
|
||||
* Paperless localization
|
||||
|
||||
* Thanks to the combined efforts of many users, Paperless is now available in English, Dutch, French and German.
|
||||
|
||||
* Thanks to `Jo Vandeginste`_, Paperless has optional support for Office documents such as .docx, .doc, .odt and more.
|
||||
|
||||
* See the :ref:`configuration<configuration-tika>` on how to enable this feature. This feature requires two additional services
|
||||
(one for parsing Office documents and metadata extraction and another for converting Office documents to PDF), and is therefore
|
||||
not enabled on default installations.
|
||||
* As with all other documents, paperless converts Office documents to PDF and stores both the original as well as the archived PDF.
|
||||
|
||||
* Dark mode
|
||||
|
||||
* Thanks to `Michael Shamoon`_, paperless now has a dark mode. Configuration is available in the settings.
|
||||
|
||||
* Other changes and additions
|
||||
|
||||
* The PDF viewer now uses a local copy of some dependencies instead of fetching them from the internet. Thanks to `slorenz`_.
|
||||
* Revamped search bar styling thanks to `Michael Shamoon`_.
|
||||
* Sorting in the document list by clicking on table headers.
|
||||
* A button was added to the document detail page that assigns a new ASN to a document.
|
||||
* Form field validation: When providing invalid input in a form (such as a duplicate ASN or no name), paperless now has visual
|
||||
indicators and clearer error messages about what's wrong.
|
||||
* Paperless disables buttons with network actions (such as save and delete) when a network action is active. This indicates that
|
||||
something is happening and prevents double clicking.
|
||||
* When using "Save & next", the title field is focussed automatically to better support keyboard editing.
|
||||
* E-Mail: Added filter rule parameters to allow inline attachments (watch out for mails with inlined images!) and attachment filename filters
|
||||
with wildcards.
|
||||
* Support for remote user authentication thanks to `Michael Shamoon`_. This is useful for hiding Paperless behind single sign on applications
|
||||
such as `authelia <https://www.authelia.com/>`_.
|
||||
* "Clear filters" has been renamed to "Reset filters" and now correctly restores the default filters on saved views. Thanks to `Michael Shamoon`_
|
||||
|
||||
* Fixes
|
||||
|
||||
* Paperless was unable to save views when "Not assigned" was chosen in one of the filter dropdowns.
|
||||
* Clearer error messages when pre and post consumption scripts do not exist.
|
||||
* The post consumption script is executed later in the consumption process. Before the change, an ID was passed to the script referring to
|
||||
a document that did not yet exist in the database.
|
||||
|
||||
paperless-ng 0.9.11
|
||||
###################
|
||||
|
||||
* Fixed an issue with the docker image not starting at all due to a configuration change of the web server.
|
||||
|
||||
|
||||
paperless-ng 0.9.10
|
||||
###################
|
||||
|
||||
* Bulk editing
|
||||
|
||||
* Thanks to `Michael Shamoon`_, we've got a new interface for the bulk editor.
|
||||
* There are some configuration options in the settings to alter the behavior.
|
||||
|
||||
* Other changes and additions
|
||||
|
||||
* Thanks to `zjean`_, paperless now publishes a webmanifest, which is useful for adding the application to home screens on mobile devices.
|
||||
* The Paperless-ng logo now navigates to the dashboard.
|
||||
* Filter for documents that don't have any correspondents, types or tags assigned.
|
||||
* Tags, types and correspondents are now sorted case insensitive.
|
||||
* Lots of preparation work for localization support.
|
||||
|
||||
* Fixes
|
||||
|
||||
* Added missing dependencies for Raspberry Pi builds.
|
||||
* Fixed an issue with plain text file consumption: Thumbnail generation failed due to missing fonts.
|
||||
* An issue with the search index reporting missing documents after bulk deletes was fixed.
|
||||
* Issue with the tag selector not clearing input correctly.
|
||||
* The consumer used to stop working when encountering an incomplete classifier model file.
|
||||
|
||||
.. note::
|
||||
|
||||
The bulk delete operations did not update the search index. Therefore, documents that you deleted remained in the index and
|
||||
caused the search to return messages about missing documents when searching. Further bulk operations will properly update
|
||||
the index.
|
||||
|
||||
However, this change is not retroactive: If you used the delete method of the bulk editor, you need to reindex your search index
|
||||
by :ref:`running the management command document_index with the argument reindex <administration-index>`.
|
||||
|
||||
paperless-ng 0.9.9
|
||||
##################
|
||||
|
||||
Christmas release!
|
||||
|
||||
* Bulk editing
|
||||
|
||||
* Paperless now supports bulk editing.
|
||||
* The following operations are available: Add and remove correspondents, tags, document types from selected documents, as well as mass-deleting documents.
|
||||
* We've got a more fancy UI in the works that makes these features more accessible, but that's not quite ready yet.
|
||||
|
||||
* Searching
|
||||
|
||||
* Paperless now supports searching for similar documents ("More like this") both from the document detail page as well as from individual search results.
|
||||
* A search score indicates how well a document matches the search query, or how similar a document is to a given reference document.
|
||||
|
||||
* Other additions and changes
|
||||
|
||||
* Clarification in the UI that the fields "Match" and "Is insensitive" are not relevant for the Auto matching algorithm.
|
||||
* New select interface for tags, types and correspondents allows filtering. This also improves tag selection. Thanks again to `Michael Shamoon`_!
|
||||
* Page navigation controls for the document viewer, thanks to `Michael Shamoon`_.
|
||||
* Layout changes to the small cards document list.
|
||||
* The dashboard now displays the username (or full name if specified in the admin) on the dashboard.
|
||||
|
||||
* Fixes
|
||||
|
||||
* An error that caused the document importer to crash was fixed.
|
||||
* An issue with changes not being possible when ``PAPERLESS_COOKIE_PREFIX`` is used was fixed.
|
||||
* The date selection filters now allow manual entry of dates.
|
||||
|
||||
* Feature Removal
|
||||
|
||||
* Most of the guesswork features have been removed. Paperless no longer tries to extract correspondents and tags from file names.
|
||||
|
||||
paperless-ng 0.9.8
|
||||
##################
|
||||
|
||||
This release addresses two severe issues with the previous release.
|
||||
|
||||
* The delete buttons for document types, correspondents and tags were not working.
|
||||
* The document section in the admin was causing internal server errors (500).
|
||||
|
||||
|
||||
paperless-ng 0.9.7
|
||||
##################
|
||||
|
||||
|
||||
* Front end
|
||||
|
||||
* Thanks to the hard work of `Michael Shamoon`_, paperless now comes with a much more streamlined UI for
|
||||
filtering documents.
|
||||
|
||||
* `Michael Shamoon`_ replaced the document preview with another component. This should fix compatibility with Safari browsers.
|
||||
|
||||
* Added buttons to the management pages to quickly show all documents with one specific tag, correspondent, or title.
|
||||
|
||||
* Paperless now stores your saved views on the server and associates them with your user account.
|
||||
This means that you can access your views on multiple devices and have separate views for different users.
|
||||
You will have to recreate your views.
|
||||
|
||||
* The GitHub and documentation links now open in new tabs/windows. Thanks to `rYR79435`_.
|
||||
|
||||
* Paperless now generates default saved view names when saving views with certain filter rules.
|
||||
|
||||
* Added a small version indicator to the front end.
|
||||
|
||||
* Other additions and changes
|
||||
|
||||
* The new filename format field ``{tag_list}`` inserts a list of tags into the filename, separated by comma.
|
||||
* The ``document_retagger`` no longer removes inbox tags or tags without matching rules.
|
||||
* The new configuration option ``PAPERLESS_COOKIE_PREFIX`` allows you to run multiple instances of paperless on different ports.
|
||||
This option enables you to be logged in into multiple instances by specifying different cookie names for each instance.
|
||||
|
||||
* Fixes
|
||||
|
||||
* Sometimes paperless would assign dates in the future to newly consumed documents.
|
||||
* The filename format fields ``{created_month}`` and ``{created_day}`` now use a leading zero for single digit values.
|
||||
* The filename format field ``{tags}`` can no longer be used without arguments.
|
||||
* Paperless was not able to consume many images (especially images from mobile scanners) due to missing DPI information.
|
||||
Paperless now assumes A4 paper size for PDF generation if no DPI information is present.
|
||||
* Documents with empty titles could not be opened from the table view due to the link being empty.
|
||||
* Fixed an issue with filenames containing special characters such as ``:`` not being accepted for upload.
|
||||
* Fixed issues with thumbnail generation for plain text files.
|
||||
|
||||
|
||||
paperless-ng 0.9.6
|
||||
##################
|
||||
|
||||
@@ -841,6 +1061,11 @@ bulk of the work on this big change.
|
||||
|
||||
* Initial release
|
||||
|
||||
.. _slorenz: https://github.com/sisao
|
||||
.. _Jo Vandeginste: https://github.com/jovandeginste
|
||||
.. _zjean: https://github.com/zjean
|
||||
.. _rYR79435: https://github.com/rYR79435
|
||||
.. _Michael Shamoon: https://github.com/shamoon
|
||||
.. _jayme-github: http://github.com/jayme-github
|
||||
.. _Brian Conn: https://github.com/TheConnMan
|
||||
.. _Christopher Luu: https://github.com/nuudles
|
||||
|
@@ -53,6 +53,12 @@ PAPERLESS_DBPASS=<password>
|
||||
|
||||
Defaults to "paperless".
|
||||
|
||||
PAPERLESS_DBSSLMODE=<mode>
|
||||
SSL mode to use when connecting to PostgreSQL.
|
||||
|
||||
See `the official documentation about sslmode <https://www.postgresql.org/docs/current/libpq-ssl.html>`_.
|
||||
|
||||
Default is ``prefer``.
|
||||
|
||||
Paths and folders
|
||||
#################
|
||||
@@ -152,6 +158,22 @@ PAPERLESS_AUTO_LOGIN_USERNAME=<username>
|
||||
|
||||
Defaults to none, which disables this feature.
|
||||
|
||||
|
||||
PAPERLESS_COOKIE_PREFIX=<str>
|
||||
Specify a prefix that is added to the cookies used by paperless to identify
|
||||
the currently logged in user. This is useful for when you're running two
|
||||
instances of paperless on the same host.
|
||||
|
||||
After changing this, you will have to login again.
|
||||
|
||||
Defaults to ``""``, which does not alter the cookie names.
|
||||
|
||||
PAPERLESS_ENABLE_HTTP_REMOTE_USER=<bool>
|
||||
Allows authentication via HTTP_REMOTE_USER which is used by some SSO
|
||||
applications.
|
||||
|
||||
Defaults to `false` which disables this feature.
|
||||
|
||||
.. _configuration-ocr:
|
||||
|
||||
OCR settings
|
||||
@@ -200,20 +222,20 @@ PAPERLESS_OCR_MODE=<mode>
|
||||
into images and puts the OCRed text on top. This works for all documents,
|
||||
however, the resulting document may be significantly larger and text
|
||||
won't appear as sharp when zoomed in.
|
||||
|
||||
|
||||
The default is ``skip``, which only performs OCR when necessary and always
|
||||
creates archived documents.
|
||||
|
||||
PAPERLESS_OCR_OUTPUT_TYPE=<type>
|
||||
Specify the the type of PDF documents that paperless should produce.
|
||||
|
||||
|
||||
* ``pdf``: Modify the PDF document as little as possible.
|
||||
* ``pdfa``: Convert PDF documents into PDF/A-2b documents, which is a
|
||||
subset of the entire PDF specification and meant for storing
|
||||
documents long term.
|
||||
* ``pdfa-1``, ``pdfa-2``, ``pdfa-3`` to specify the exact version of
|
||||
PDF/A you wish to use.
|
||||
|
||||
|
||||
If not specified, ``pdfa`` is used. Remember that paperless also keeps
|
||||
the original input file as well as the archived version.
|
||||
|
||||
@@ -265,9 +287,69 @@ PAPERLESS_OCR_USER_ARG=<json>
|
||||
|
||||
.. code:: json
|
||||
|
||||
{"deskew": true, "optimize": 3, "unpaper_args": "--pre-rotate 90"}
|
||||
|
||||
|
||||
{"deskew": true, "optimize": 3, "unpaper_args": "--pre-rotate 90"}
|
||||
|
||||
.. _configuration-tika:
|
||||
|
||||
Tika settings
|
||||
#############
|
||||
|
||||
Paperless can make use of `Tika <https://tika.apache.org/>`_ and
|
||||
`Gotenberg <https://thecodingmachine.github.io/gotenberg/>`_ for parsing and
|
||||
converting "Office" documents (such as ".doc", ".xlsx" and ".odt"). If you
|
||||
wish to use this, you must provide a Tika server and a Gotenberg server,
|
||||
configure their endpoints, and enable the feature.
|
||||
|
||||
PAPERLESS_TIKA_ENABLED=<bool>
|
||||
Enable (or disable) the Tika parser.
|
||||
|
||||
Defaults to false.
|
||||
|
||||
PAPERLESS_TIKA_ENDPOINT=<url>
|
||||
Set the endpoint URL were Paperless can reach your Tika server.
|
||||
|
||||
Defaults to "http://localhost:9998".
|
||||
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT=<url>
|
||||
Set the endpoint URL were Paperless can reach your Gotenberg server.
|
||||
|
||||
Defaults to "http://localhost:3000".
|
||||
|
||||
If you run paperless on docker, you can add those services to the docker-compose
|
||||
file (see the provided ``docker-compose.tika.yml`` file for reference). The changes
|
||||
requires are as follows:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
services:
|
||||
# ...
|
||||
|
||||
webserver:
|
||||
# ...
|
||||
|
||||
environment:
|
||||
# ...
|
||||
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
# ...
|
||||
|
||||
gotenberg:
|
||||
image: thecodingmachine/gotenberg
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DISABLE_GOOGLE_CHROME: 1
|
||||
|
||||
tika:
|
||||
image: apache/tika
|
||||
restart: unless-stopped
|
||||
|
||||
Add the configuration variables to the environment of the webserver (alternatively
|
||||
put the configuration in the ``docker-compose.env`` file) and add the additional
|
||||
services below the webserver service. Watch out for indentation.
|
||||
|
||||
Software tweaks
|
||||
###############
|
||||
|
||||
@@ -294,8 +376,26 @@ PAPERLESS_THREADS_PER_WORKER=<num>
|
||||
use a higher thread per worker count.
|
||||
|
||||
The default is a balance between the two, according to your CPU core count,
|
||||
with a slight favor towards threads per worker, and using as much cores as
|
||||
possible.
|
||||
with a slight favor towards threads per worker, and leaving at least one core
|
||||
free for other tasks:
|
||||
|
||||
+----------------+---------+---------+
|
||||
| CPU core count | Workers | Threads |
|
||||
+----------------+---------+---------+
|
||||
| 1 | 1 | 1 |
|
||||
+----------------+---------+---------+
|
||||
| 2 | 1 | 1 |
|
||||
+----------------+---------+---------+
|
||||
| 4 | 1 | 3 |
|
||||
+----------------+---------+---------+
|
||||
| 6 | 2 | 2 |
|
||||
+----------------+---------+---------+
|
||||
| 8 | 2 | 3 |
|
||||
+----------------+---------+---------+
|
||||
| 12 | 3 | 3 |
|
||||
+----------------+---------+---------+
|
||||
| 16 | 3 | 5 |
|
||||
+----------------+---------+---------+
|
||||
|
||||
If you only specify PAPERLESS_TASK_WORKERS, paperless will adjust
|
||||
PAPERLESS_THREADS_PER_WORKER automatically.
|
||||
@@ -309,11 +409,14 @@ PAPERLESS_TIME_ZONE=<timezone>
|
||||
Defaults to UTC.
|
||||
|
||||
|
||||
.. _configuration-polling:
|
||||
|
||||
PAPERLESS_CONSUMER_POLLING=<num>
|
||||
If paperless won't find documents added to your consume folder, it might
|
||||
not be able to automatically detect filesystem changes. In that case,
|
||||
specify a polling interval in seconds here, which will then cause paperless
|
||||
to periodically check your consumption directory for changes.
|
||||
to periodically check your consumption directory for changes. This will also
|
||||
disable listening for file system changes with ``inotify``.
|
||||
|
||||
Defaults to 0, which disables polling and uses filesystem notifications.
|
||||
|
||||
@@ -338,6 +441,9 @@ PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=<bool>
|
||||
E.g. <CONSUMPTION_DIR>/foo/bar/file.pdf will add the tags "foo" and "bar" to
|
||||
the consumed file. Paperless will create any tags that don't exist yet.
|
||||
|
||||
This is useful for sorting documents with certain tags such as ``car`` or
|
||||
``todo`` prior to consumption. These folders won't be deleted.
|
||||
|
||||
PAPERLESS_CONSUMER_RECURSIVE must be enabled for this to work.
|
||||
|
||||
Defaults to false.
|
||||
@@ -390,11 +496,28 @@ PAPERLESS_FILENAME_DATE_ORDER=<format>
|
||||
|
||||
Defaults to none, which disables this feature.
|
||||
|
||||
PAPERLESS_FILENAME_PARSE_TRANSFORMS
|
||||
Transforms filenames before they are processed by paperless. See
|
||||
:ref:`advanced-transforming_filenames` for details.
|
||||
PAPERLESS_THUMBNAIL_FONT_NAME=<filename>
|
||||
Paperless creates thumbnails for plain text files by rendering the content
|
||||
of the file on an image and uses a predefined font for that. This
|
||||
font can be changed here.
|
||||
|
||||
Note that this won't have any effect on already generated thumbnails.
|
||||
|
||||
Defaults to ``/usr/share/fonts/liberation/LiberationSerif-Regular.ttf``.
|
||||
|
||||
PAPERLESS_IGNORE_DATES=<string>
|
||||
Paperless parses a documents creation date from filename and file content.
|
||||
You may specify a comma separated list of dates that should be ignored during
|
||||
this process. This is useful for special dates (like date of birth) that appear
|
||||
in documents regularly but are very unlikely to be the documents creation date.
|
||||
|
||||
You may specify dates in a multitude of formats supported by dateparser (see
|
||||
https://dateparser.readthedocs.io/en/latest/#popular-formats) but as the dates
|
||||
need to be comma separated, the options are limited.
|
||||
Example: "2020-12-02,22.04.1999"
|
||||
|
||||
Defaults to an empty string to not ignore any dates.
|
||||
|
||||
Defaults to none, which disables this feature.
|
||||
|
||||
Binaries
|
||||
########
|
||||
|
@@ -25,8 +25,9 @@ This section describes the steps you need to take to start development on paperl
|
||||
|
||||
* Python 3.6.
|
||||
* All dependencies listed in the :ref:`Bare metal route <setup-bare_metal>`
|
||||
* redis. You can either install redis or use the included scritps/start-redis.sh
|
||||
to use docker to fire up a redis instance.
|
||||
* redis. You can either install redis or use the included scritps/start-services.sh
|
||||
to use docker to fire up a redis instance (and some other services such as tika,
|
||||
gotenberg and a postgresql server).
|
||||
|
||||
Back end development
|
||||
====================
|
||||
@@ -38,7 +39,7 @@ Install the python dependencies by performing ``pipenv install --dev`` in the sr
|
||||
This will also create a virtual environment, which you can enter with ``pipenv shell`` or
|
||||
execute one-shot commands in with ``pipenv run``.
|
||||
|
||||
In ``src/paperless.conf``, enable debug mode.
|
||||
Copy ``paperless.conf.example`` to ``paperless.conf`` and enable debug mode.
|
||||
|
||||
Configure the IDE to use the src/ folder as the base source folder. Configure the following
|
||||
launch configurations in your IDE:
|
||||
@@ -102,18 +103,11 @@ In order to build the front end and serve it as part of django, execute
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ ng build --prod --output-path ../src/documents/static/frontend/
|
||||
$ ng build --prod
|
||||
|
||||
This will build the front end and put it in a location from which the Django server will serve
|
||||
it as static content. This way, you can verify that authentication is working.
|
||||
|
||||
Making a release
|
||||
================
|
||||
|
||||
Execute the ``make-release.sh <ver>`` script.
|
||||
|
||||
This will test and assemble everything and also build and tag a docker image.
|
||||
|
||||
|
||||
Extending Paperless
|
||||
===================
|
||||
|
14
docs/faq.rst
14
docs/faq.rst
@@ -52,6 +52,8 @@ out of that folder to use them elsewhere. Here are a couple notes about that.
|
||||
* PDF documents, PNG images, JPEG images, TIFF images and GIF images are processed with OCR and converted into PDF documents.
|
||||
* Plain text documents are supported as well and are added verbatim
|
||||
to paperless.
|
||||
* With the optional Tika integration enabled (see :ref:`Configuration <configuration-tika>`), Paperless also supports various
|
||||
Office documents (.docx, .doc, odt, .ppt, .pptx, .odp, .xls, .xlsx, .ods).
|
||||
|
||||
Paperless determines the type of a file by inspecting its content. The
|
||||
file extensions do not matter.
|
||||
@@ -73,10 +75,14 @@ in your browser and paperless has to do much less work to serve the data.
|
||||
|
||||
**Q:** *How do I install paperless-ng on Raspberry Pi?*
|
||||
|
||||
**A:** There is no docker image for ARM available. If you know how to build
|
||||
that automatically, I'm all ears. For now, you have to grab the latest release
|
||||
archive from the project page and build the image yourself. The release comes
|
||||
with the front end already compiled, so you don't have to do this on the Pi.
|
||||
**A:** Docker images are available for arm and arm64 hardware, so just follow
|
||||
the docker-compose instructions, or go the bare metal route.
|
||||
|
||||
**Q:** *How do I run this on unRaid?*
|
||||
|
||||
**A:** Head over to `<https://github.com/selfhosters/unRAID-CA-templates>`_,
|
||||
`Uli Fahrer <https://github.com/Tooa>`_ created a container template for that.
|
||||
I don't exactly know how to use that though, since I don't use unRaid.
|
||||
|
||||
**Q:** *How do I run this on my toaster?*
|
||||
|
||||
|
@@ -10,6 +10,9 @@ scanner you use, but sometimes finding a scanner that will write to an FTP,
|
||||
NFS, or SMB server can be difficult. This page is here to help you find one
|
||||
that works right for you based on recommendations from other Paperless users.
|
||||
|
||||
Physical scanners
|
||||
=================
|
||||
|
||||
+---------+----------------+-----+-----+-----+----------------+
|
||||
| Brand | Model | Supports | Recommended By |
|
||||
+---------+----------------+-----+-----+-----+----------------+
|
||||
@@ -25,6 +28,8 @@ that works right for you based on recommendations from other Paperless users.
|
||||
+---------+----------------+-----+-----+-----+----------------+
|
||||
| Fujitsu | `ix500`_ | yes | | yes | `eonist`_ |
|
||||
+---------+----------------+-----+-----+-----+----------------+
|
||||
| Epson | `WF-7710DWF`_ | yes | | yes | `Skylinar`_ |
|
||||
+---------+----------------+-----+-----+-----+----------------+
|
||||
| Fujitsu | `S1300i`_ | yes | | yes | `jonaswinkler`_|
|
||||
+---------+----------------+-----+-----+-----+----------------+
|
||||
|
||||
@@ -32,7 +37,8 @@ that works right for you based on recommendations from other Paperless users.
|
||||
.. _MFC-J6930DW: https://www.brother.ca/en/p/MFCJ6930DW
|
||||
.. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw
|
||||
.. _MFC-9142CDN: https://www.brother.co.uk/printers/laser-printers/mfc9140cdn
|
||||
.. _ix500: https://www.fujitsu.com/global/products/computing/peripheral/scanners/scansnap/ix500/
|
||||
.. _ix500: http://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/ix500/
|
||||
.. _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
|
||||
@@ -40,4 +46,27 @@ that works right for you based on recommendations from other Paperless users.
|
||||
.. _bmsleight: https://github.com/bmsleight
|
||||
.. _eonist: https://github.com/eonist
|
||||
.. _REOLDEV: https://github.com/REOLDEV
|
||||
.. _Skylinar: https://github.com/Skylinar
|
||||
.. _jonaswinkler: https://github.com/jonaswinkler
|
||||
|
||||
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`_ |
|
||||
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
|
||||
|
||||
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.
|
||||
|
||||
.. _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
|
||||
|
||||
.. _hannahswain: https://github.com/hannahswain
|
||||
|
220
docs/setup.rst
220
docs/setup.rst
@@ -3,35 +3,6 @@
|
||||
Setup
|
||||
*****
|
||||
|
||||
Download
|
||||
########
|
||||
|
||||
Go to the project page on GitHub and download the
|
||||
`latest release <https://github.com/jonaswinkler/paperless-ng/releases>`_.
|
||||
There are multiple options available.
|
||||
|
||||
* Download the dockerfiles archive if you want to pull paperless from
|
||||
Docker Hub.
|
||||
|
||||
* Download the dist archive and extract it if you want to build the docker image
|
||||
yourself or want to install paperless without docker.
|
||||
|
||||
.. hint::
|
||||
|
||||
In contrast to paperless, the recommended way to get and update paperless-ng
|
||||
is not to pull the entire git repository. Paperless-ng includes artifacts
|
||||
that need to be compiled, and that's already done for you in the release.
|
||||
|
||||
.. admonition:: Want to try out paperless-ng before migrating?
|
||||
|
||||
The release contains a file ``.env`` which sets the docker-compose project
|
||||
name to "paperless", which is the same as before and instructs docker-compose
|
||||
to reuse and upgrade your paperless volumes.
|
||||
|
||||
Just rename the project name in that file to anything else and docker-compose
|
||||
will create fresh volumes for you!
|
||||
|
||||
|
||||
Overview of Paperless-ng
|
||||
########################
|
||||
|
||||
@@ -110,20 +81,35 @@ Installation
|
||||
|
||||
You can go multiple routes with setting up and running Paperless:
|
||||
|
||||
* The `docker route`_
|
||||
* The `bare metal route`_
|
||||
* :ref:`Pull the image from Docker Hub <setup-docker_hub>`
|
||||
* :ref:`Build the Docker image yourself <setup-docker_build>`
|
||||
* :ref:`Install Paperless directly on your system (bare metal) <setup-bare_metal>`
|
||||
|
||||
The `docker route`_ is quick & easy. This is the recommended route. This configures all the stuff
|
||||
The Docker routes are quick & easy. These are the recommended routes. This configures all the stuff
|
||||
from above automatically so that it just works and uses sensible defaults for all configuration options.
|
||||
|
||||
The `bare metal route`_ is more complicated to setup but makes it easier
|
||||
The bare metal route is more complicated to setup but makes it easier
|
||||
should you want to contribute some code back. You need to configure and
|
||||
run the above mentioned components yourself.
|
||||
|
||||
Docker Route
|
||||
============
|
||||
.. _setup-docker_hub:
|
||||
|
||||
1. Install `Docker`_ and `docker-compose`_. [#compose]_
|
||||
Install Paperless from Docker Hub
|
||||
=================================
|
||||
|
||||
1. Go to the `/docker/compose directory on the project page <https://github.com/jonaswinkler/paperless-ng/tree/master/docker/compose>`_
|
||||
and download one of the ``docker-compose.*.yml`` files, depending on which database backend you
|
||||
want to use. Rename this file to `docker-compose.yml`.
|
||||
If you want to enable optional support for Office documents, download a file with ``-tika`` in its name.
|
||||
Download the ``docker-compose.env`` file and the ``.env`` file as well and store them
|
||||
in the same directory.
|
||||
|
||||
.. hint::
|
||||
|
||||
For new installations, it is recommended to use PostgreSQL as the database
|
||||
backend.
|
||||
|
||||
2. Install `Docker`_ and `docker-compose`_.
|
||||
|
||||
.. caution::
|
||||
|
||||
@@ -140,15 +126,7 @@ Docker Route
|
||||
.. _Docker installation guide: https://docs.docker.com/engine/installation/
|
||||
.. _docker-compose installation guide: https://docs.docker.com/compose/install/
|
||||
|
||||
2. Copy either ``docker-compose.sqlite.yml`` or ``docker-compose.postgres.yml`` to
|
||||
``docker-compose.yml``, depending on which database backend you want to use.
|
||||
|
||||
.. hint::
|
||||
|
||||
For new installations, it is recommended to use PostgreSQL as the database
|
||||
backend.
|
||||
|
||||
2. Modify ``docker-compose.yml`` to your preferences. You may want to change the path
|
||||
3. Modify ``docker-compose.yml`` to your preferences. You may want to change the path
|
||||
to the consumption directory in this file. Find the line that specifies where
|
||||
to mount the consumption directory:
|
||||
|
||||
@@ -165,7 +143,7 @@ Docker Route
|
||||
Don't change the part after the colon or paperless wont find your documents.
|
||||
|
||||
|
||||
3. Modify ``docker-compose.env``, following the comments in the file. The
|
||||
4. Modify ``docker-compose.env``, following the comments in the file. The
|
||||
most important change is to set ``USERMAP_UID`` and ``USERMAP_GID``
|
||||
to the uid and gid of your user on the host system. This ensures that
|
||||
both the docker container and you on the host machine have write access
|
||||
@@ -175,14 +153,21 @@ Docker Route
|
||||
|
||||
.. note::
|
||||
|
||||
You can use any settings from the file ``paperless.conf`` in this file.
|
||||
You can use any settings from the file ``paperless.conf.example`` in this file.
|
||||
Have a look at :ref:`configuration` to see whats available.
|
||||
|
||||
4. Run ``docker-compose up -d``. This will create and start the necessary
|
||||
containers. This will also build the image of paperless if you grabbed the
|
||||
source archive.
|
||||
.. caution::
|
||||
|
||||
5. To be able to login, you will need a super user. To create it, execute the
|
||||
Certain file systems such as NFS network shares don't support file system
|
||||
notifications with ``inotify``. When storing the consumption directory
|
||||
on such a file system, paperless will be unable to pick up new files
|
||||
with the default configuration. You will need to use ``PAPERLESS_CONSUMER_POLLING``,
|
||||
which will disable inotify. See :ref:`here <configuration-polling>`.
|
||||
|
||||
5. Run ``docker-compose up -d``. This will create and start the necessary
|
||||
containers.
|
||||
|
||||
6. To be able to login, you will need a super user. To create it, execute the
|
||||
following command:
|
||||
|
||||
.. code-block:: shell-session
|
||||
@@ -192,7 +177,7 @@ Docker Route
|
||||
This will prompt you to set a username, an optional e-mail address and
|
||||
finally a password.
|
||||
|
||||
6. The default ``docker-compose.yml`` exports the webserver on your local port
|
||||
7. The default ``docker-compose.yml`` exports the webserver on your local port
|
||||
8000. If you haven't adapted this, you should now be able to visit your
|
||||
Paperless instance at ``http://127.0.0.1:8000``. You can login with the
|
||||
user and password you just created.
|
||||
@@ -200,11 +185,49 @@ Docker Route
|
||||
.. _Docker: https://www.docker.com/
|
||||
.. _docker-compose: https://docs.docker.com/compose/install/
|
||||
|
||||
.. [#compose] You of course don't have to use docker-compose, but it
|
||||
simplifies deployment immensely. If you know your way around Docker, feel
|
||||
free to tinker around without using compose!
|
||||
.. _setup-docker_build:
|
||||
|
||||
.. _`setup-bare_metal`:
|
||||
Build the docker image yourself
|
||||
===============================
|
||||
|
||||
1. Clone the entire repository of paperless:
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
git clone https://github.com/jonaswinkler/paperless-ng
|
||||
|
||||
The master branch always reflects the latest stable version.
|
||||
|
||||
2. Copy one of the ``docker/compose/docker-compose.*.yml`` to ``docker-compose.yml`` in the root folder,
|
||||
depending on which database backend you want to use. Copy
|
||||
``docker-compose.env`` into the project root as well.
|
||||
|
||||
3. In the ``docker-compose.yml`` file, find the line that instructs docker-compose to pull the paperless image from Docker Hub:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
webserver:
|
||||
image: jonaswinkler/paperless-ng:latest
|
||||
|
||||
and replace it with a line that instructs docker-compose to build the image from the current working directory instead:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
webserver:
|
||||
build: .
|
||||
|
||||
4. Run the ``compile-frontend.sh`` script. This requires ``node`` and ``npm >= v15``.
|
||||
|
||||
5. Follow steps 2 to 7 of :ref:`setup-docker_hub`. When asked to run
|
||||
``docker-compose up -d`` to start the containers, do
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ docker-compose build
|
||||
|
||||
before that to build the image.
|
||||
|
||||
.. _setup-bare_metal:
|
||||
|
||||
Bare Metal Route
|
||||
================
|
||||
@@ -219,12 +242,14 @@ writing. Windows is not and will never be supported.
|
||||
* ``python3-pip``, optionally ``pipenv`` for package installation
|
||||
* ``python3-dev``
|
||||
|
||||
* ``fonts-liberation`` for generating thumbnails for plain text files
|
||||
* ``imagemagick`` >= 6 for PDF conversion
|
||||
* ``optipng`` for optimising thumbnails
|
||||
* ``optipng`` for optimizing thumbnails
|
||||
* ``gnupg`` for handling encrypted documents
|
||||
* ``libpoppler-cpp-dev`` for PDF to text conversion
|
||||
* ``libmagic-dev`` for mime type detection
|
||||
* ``libpq-dev`` for PostgreSQL
|
||||
* ``libmagic-dev`` for mime type detection
|
||||
* ``mime-support`` for mime type detection
|
||||
|
||||
These dependencies are required for OCRmyPDF, which is used for text recognition.
|
||||
|
||||
@@ -239,17 +264,22 @@ writing. Windows is not and will never be supported.
|
||||
* ``tesseract-ocr`` >= 4.0.0 for OCR
|
||||
* ``tesseract-ocr`` language packs (``tesseract-ocr-eng``, ``tesseract-ocr-deu``, etc)
|
||||
|
||||
On Raspberry Pi, these libraries are required as well:
|
||||
|
||||
* ``libatlas-base-dev``
|
||||
* ``libxslt1-dev``
|
||||
|
||||
You will also need ``build-essential``, ``python3-setuptools`` and ``python3-wheel``
|
||||
for installing some of the python dependencies. You can remove that
|
||||
again after installation.
|
||||
for installing some of the python dependencies.
|
||||
|
||||
2. Install ``redis`` >= 5.0 and configure it to start automatically.
|
||||
|
||||
3. Optional. Install ``postgresql`` and configure a database, user and password for paperless. If you do not wish
|
||||
to use PostgreSQL, SQLite is avialable as well.
|
||||
|
||||
4. Get the release archive. If you pull the git repo as it is, you also have to compile the front end by yourself.
|
||||
Extract the frontend to a place from where you wish to execute it, such as ``/opt/paperless``.
|
||||
4. Get the release archive from `<https://github.com/jonaswinkler/paperless-ng/releases>`_.
|
||||
If you clone the git repo as it is, you also have to compile the front end by yourself.
|
||||
Extract the archive to a place from where you wish to execute it, such as ``/opt/paperless``.
|
||||
|
||||
5. Configure paperless. See :ref:`configuration` for details. Edit the included ``paperless.conf`` and adjust the
|
||||
settings to your needs. Required settings for getting paperless running are:
|
||||
@@ -262,7 +292,7 @@ writing. Windows is not and will never be supported.
|
||||
paperless stores its data. If you like, you can point both to the same directory.
|
||||
* ``PAPERLESS_SECRET_KEY`` should be a random sequence of characters. It's used for authentication. Failure
|
||||
to do so allows third parties to forge authentication credentials.
|
||||
|
||||
|
||||
Many more adjustments can be made to paperless, especially the OCR part. The following options are recommended
|
||||
for everyone:
|
||||
|
||||
@@ -271,7 +301,7 @@ writing. Windows is not and will never be supported.
|
||||
|
||||
6. Setup permissions. Create a system users under which you wish to run paperless. Ensure that these directories exist
|
||||
and that the user has write permissions to the following directories
|
||||
|
||||
|
||||
* ``/opt/paperless/media``
|
||||
* ``/opt/paperless/data``
|
||||
* ``/opt/paperless/consume``
|
||||
@@ -285,9 +315,6 @@ writing. Windows is not and will never be supported.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# This collects static files from paperless and django.
|
||||
python3 manage.py collectstatic --clear --no-input
|
||||
|
||||
# This creates the database schema.
|
||||
python3 manage.py migrate
|
||||
|
||||
@@ -300,7 +327,7 @@ writing. Windows is not and will never be supported.
|
||||
|
||||
# This collects static files from paperless and django.
|
||||
python3 manage.py runserver
|
||||
|
||||
|
||||
and pointing your browser to http://localhost:8000/.
|
||||
|
||||
.. warning::
|
||||
@@ -353,13 +380,18 @@ writing. Windows is not and will never be supported.
|
||||
.. code::
|
||||
|
||||
<policy domain="coder" rights="none" pattern="PDF" />
|
||||
|
||||
|
||||
to
|
||||
|
||||
.. code::
|
||||
|
||||
<policy domain="coder" rights="read|write" pattern="PDF" />
|
||||
|
||||
13. Optional: Install the `jbig2enc <https://ocrmypdf.readthedocs.io/en/latest/jbig2.html>`_
|
||||
encoder. This will reduce the size of generated PDF documents. You'll most likely need
|
||||
to compile this by yourself, because this software has been patented until around 2017 and
|
||||
binary packages are not available for most distributions.
|
||||
|
||||
Migration to paperless-ng
|
||||
#########################
|
||||
|
||||
@@ -393,32 +425,27 @@ Migration to paperless-ng is then performed in a few simple steps:
|
||||
paperless.
|
||||
|
||||
3. Download the latest release of paperless-ng. You can either go with the
|
||||
docker-compose files or use the archive to build the image yourself.
|
||||
docker-compose files from `here <https://github.com/jonaswinkler/paperless-ng/tree/master/docker/compose>`_
|
||||
or clone the repository to build the image yourself (see :ref:`above <setup-docker_build>`).
|
||||
You can either replace your current paperless folder or put paperless-ng
|
||||
in a different location.
|
||||
|
||||
.. caution::
|
||||
|
||||
The release include a ``.env`` file. This will set the
|
||||
Paperless includes a ``.env`` file. This will set the
|
||||
project name for docker compose to ``paperless`` so that paperless-ng will
|
||||
automatically reuse your existing paperless volumes. When you start it, it
|
||||
will migrate your existing data. After that, your old paperless installation
|
||||
will be incompatible with the migrated volumes.
|
||||
|
||||
4. Copy the ``docker-compose.sqlite.yml`` file to ``docker-compose.yml``.
|
||||
4. Download the ``docker-compose.sqlite.yml`` file to ``docker-compose.yml``.
|
||||
If you want to switch to PostgreSQL, do that after you migrated your existing
|
||||
SQLite database.
|
||||
|
||||
5. Adjust ``docker-compose.yml`` and
|
||||
``docker-compose.env`` to your needs.
|
||||
See `docker route`_ for details on which edits are advised.
|
||||
5. Adjust ``docker-compose.yml`` and ``docker-compose.env`` to your needs.
|
||||
See :ref:`setup-docker_hub` for details on which edits are advised.
|
||||
|
||||
6. Since ``docker-compose`` would just use the the old paperless image, we need to
|
||||
manually build a new image:
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ docker-compose build
|
||||
6. :ref:`Update paperless. <administration-updating>`
|
||||
|
||||
7. In order to find your existing documents with the new search feature, you need
|
||||
to invoke a one-time operation that will create the search index:
|
||||
@@ -426,7 +453,7 @@ Migration to paperless-ng is then performed in a few simple steps:
|
||||
.. code:: shell-session
|
||||
|
||||
$ docker-compose run --rm webserver document_index reindex
|
||||
|
||||
|
||||
This will migrate your database and create the search index. After that,
|
||||
paperless will take care of maintaining the index by itself.
|
||||
|
||||
@@ -439,7 +466,7 @@ Migration to paperless-ng is then performed in a few simple steps:
|
||||
This will run paperless in the background and automatically start it on system boot.
|
||||
|
||||
9. Paperless installed a permanent redirect to ``admin/`` in your browser. This
|
||||
redirect is still in place and prevents access to the new UI. Clear
|
||||
redirect is still in place and prevents access to the new UI. Clear your
|
||||
browsing cache in order to fix this.
|
||||
|
||||
10. Optionally, follow the instructions below to migrate your existing data to PostgreSQL.
|
||||
@@ -460,6 +487,15 @@ management commands as below.
|
||||
load data from an old database schema in SQLite into a newer database
|
||||
schema in PostgreSQL, you will run into trouble.
|
||||
|
||||
.. warning::
|
||||
|
||||
On some database fields, PostgreSQL enforces predefined limits on maximum
|
||||
length, whereas SQLite does not. The fields in question are the title of documents
|
||||
(128 characters), names of document types, tags and correspondents (128 characters),
|
||||
and filenames (1024 characters). If you have data in these fields that surpasses these
|
||||
limits, migration to PostgreSQL is not possible and will fail with an error.
|
||||
|
||||
|
||||
1. Stop paperless, if it is running.
|
||||
2. Tell paperless to use PostgreSQL:
|
||||
|
||||
@@ -478,9 +514,9 @@ management commands as below.
|
||||
|
||||
$ cd /path/to/paperless
|
||||
$ docker-compose run --rm webserver /bin/bash
|
||||
|
||||
|
||||
This will launch the container and initialize the PostgreSQL database.
|
||||
|
||||
|
||||
b) Without docker, open a shell in your virtual environment, switch to
|
||||
the ``src`` directory and create the database schema:
|
||||
|
||||
@@ -490,7 +526,7 @@ management commands as below.
|
||||
$ pipenv shell
|
||||
$ cd src
|
||||
$ python3 manage.py migrate
|
||||
|
||||
|
||||
This will not copy any data yet.
|
||||
|
||||
4. Dump your data from SQLite:
|
||||
@@ -498,7 +534,7 @@ management commands as below.
|
||||
.. code:: shell-session
|
||||
|
||||
$ python3 manage.py dumpdata --database=sqlite --exclude=contenttypes --exclude=auth.Permission > data.json
|
||||
|
||||
|
||||
5. Load your data into PostgreSQL:
|
||||
|
||||
.. code:: shell-session
|
||||
@@ -549,7 +585,7 @@ as well.
|
||||
Considerations for less powerful devices
|
||||
########################################
|
||||
|
||||
Paperless runs on Raspberry Pi. However, some things are rather slow on the Pi and
|
||||
Paperless runs on Raspberry Pi. However, some things are rather slow on the Pi and
|
||||
configuring some options in paperless can help improve performance immensely:
|
||||
|
||||
* Stick with SQLite to save some resources.
|
||||
@@ -571,17 +607,15 @@ configuring some options in paperless can help improve performance immensely:
|
||||
For details, refer to :ref:`configuration`.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
Updating the :ref:`automatic matching algorithm <advanced-automatic_matching>`
|
||||
takes quite a bit of time. However, the update mechanism checks if your
|
||||
data has changed before doing the heavy lifting. If you experience the
|
||||
data has changed before doing the heavy lifting. If you experience the
|
||||
algorithm taking too much cpu time, consider changing the schedule in the
|
||||
admin interface to daily. You can also manually invoke the task
|
||||
by changing the date and time of the next run to today/now.
|
||||
|
||||
The actual matching of the algorithm is fast and works on Raspberry Pi as
|
||||
The actual matching of the algorithm is fast and works on Raspberry Pi as
|
||||
well as on any other device.
|
||||
|
||||
|
||||
|
||||
.. _redis: https://redis.io/
|
||||
|
@@ -34,12 +34,15 @@ directory at startup, but won't find any other files added later, check out
|
||||
the configuration file and enable filesystem polling with the setting
|
||||
``PAPERLESS_CONSUMER_POLLING``.
|
||||
|
||||
This will disable listening to filesystem changes with inotify and paperless will
|
||||
manually check the consumption directory for changes instead.
|
||||
|
||||
Operation not permitted
|
||||
#######################
|
||||
|
||||
You might see errors such as:
|
||||
|
||||
.. code::
|
||||
.. code:: shell-session
|
||||
|
||||
chown: changing ownership of '../export': Operation not permitted
|
||||
|
||||
@@ -49,3 +52,29 @@ to these folders. This happens when pointing these directories to NFS shares,
|
||||
for example.
|
||||
|
||||
Ensure that `chown` is possible on these directories.
|
||||
|
||||
Classifier error: No training data available
|
||||
############################################
|
||||
|
||||
This indicates that the Auto matching algorithm found no documents to learn from.
|
||||
This may have two reasons:
|
||||
|
||||
* You don't use the Auto matching algorithm: The error can be safely ignored in this case.
|
||||
* You are using the Auto matching algorithm: The classifier explicitly excludes documents
|
||||
with Inbox tags. Verify that there are documents in your archive without inbox tags.
|
||||
The algorithm will only learn from documents not in your inbox.
|
||||
|
||||
Permission denied errors in the consumption directory
|
||||
#####################################################
|
||||
|
||||
You might encounter errors such as:
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
The following error occured while consuming document.pdf: [Errno 13] Permission denied: '/usr/src/paperless/src/../consume/document.pdf'
|
||||
|
||||
This happens when paperless does not have permission to delete files inside the consumption directory.
|
||||
Ensure that ``USERMAP_UID`` and ``USERMAP_GID`` are set to the user id and group id you use on the host operating system, if these are
|
||||
different from ``1000``. See :ref:`setup-docker_hub`.
|
||||
|
||||
Also ensure that you are able to read and write to the consumption directory on the host.
|
||||
|
@@ -57,9 +57,6 @@ Adding documents to paperless
|
||||
#############################
|
||||
|
||||
Once you've got Paperless setup, you need to start feeding documents into it.
|
||||
Currently, there are four options: the consumption directory, the dashboard, IMAP (email), and
|
||||
HTTP POST.
|
||||
|
||||
When adding documents to paperless, it will perform the following operations on
|
||||
your documents:
|
||||
|
||||
@@ -112,6 +109,19 @@ Dashboard upload
|
||||
The dashboard has a file drop field to upload documents to paperless. Simply drag a file
|
||||
onto this field or select a file with the file dialog. Multiple files are supported.
|
||||
|
||||
|
||||
.. _usage-mobile_upload:
|
||||
|
||||
Mobile upload
|
||||
=============
|
||||
|
||||
The mobile app over at `<https://github.com/qcasey/paperless_share>`_ allows Android users
|
||||
to share any documents with paperless. This can be combined with any of the mobile
|
||||
scanning apps out there, such as Office Lens.
|
||||
|
||||
Furthermore, there is the `Paperless App <https://github.com/bauerj/paperless_app>`_ as well,
|
||||
which not only has document upload, but also document browsing and download features.
|
||||
|
||||
.. _usage-email:
|
||||
|
||||
IMAP (Email)
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#PAPERLESS_DBNAME=paperless
|
||||
#PAPERLESS_DBUSER=paperless
|
||||
#PAPERLESS_DBPASS=paperless
|
||||
#PAPERLESS_DBSSLMODE=prefer
|
||||
|
||||
# Paths and folders
|
||||
|
||||
@@ -30,6 +31,8 @@
|
||||
#PAPERLESS_FORCE_SCRIPT_NAME=
|
||||
#PAPERLESS_STATIC_URL=/static/
|
||||
#PAPERLESS_AUTO_LOGIN_USERNAME=
|
||||
#PAPERLESS_COOKIE_PREFIX=
|
||||
#PAPERLESS_ENABLE_HTTP_REMOTE_USER=false
|
||||
|
||||
# OCR settings
|
||||
|
||||
@@ -38,7 +41,7 @@
|
||||
#PAPERLESS_OCR_OUTPUT_TYPE=pdfa
|
||||
#PAPERLESS_OCR_PAGES=1
|
||||
#PAPERLESS_OCR_IMAGE_DPI=300
|
||||
#PAPERLESS_OCR_USER_ARG={}
|
||||
#PAPERLESS_OCR_USER_ARGS={}
|
||||
#PAPERLESS_CONVERT_MEMORY_LIMIT=0
|
||||
#PAPERLESS_CONVERT_TMPDIR=/var/tmp/paperless
|
||||
|
||||
@@ -49,10 +52,20 @@
|
||||
#PAPERLESS_TIME_ZONE=UTC
|
||||
#PAPERLESS_CONSUMER_POLLING=10
|
||||
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
|
||||
#PAPERLESS_CONSUMER_RECURSIVE=false
|
||||
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
|
||||
#PAPERLESS_OPTIMIZE_THUMBNAILS=true
|
||||
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
|
||||
#PAPERLESS_FILENAME_DATE_ORDER=YMD
|
||||
#PAPERLESS_FILENAME_PARSE_TRANSFORMS=[]
|
||||
#PAPERLESS_THUMBNAIL_FONT_NAME=
|
||||
#PAPERLESS_IGNORE_DATES=
|
||||
|
||||
# Tika settings
|
||||
|
||||
#PAPERLESS_TIKA_ENABLED=false
|
||||
#PAPERLESS_TIKA_ENDPOINT=http://localhost:9998
|
||||
#PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://localhost:3000
|
||||
|
||||
# Binaries
|
||||
|
||||
|
74
requirements.txt
Normal file
74
requirements.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
#
|
||||
# These requirements were autogenerated by pipenv
|
||||
# To regenerate from the project's Pipfile, run:
|
||||
#
|
||||
# pipenv lock --requirements
|
||||
#
|
||||
|
||||
-i https://pypi.python.org/simple
|
||||
--extra-index-url https://www.piwheels.org/simple
|
||||
arrow==0.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
asgiref==3.3.1; python_version >= '3.5'
|
||||
blessed==1.17.12
|
||||
certifi==2020.12.5
|
||||
cffi==1.14.4
|
||||
chardet==4.0.0; python_version >= '3.1'
|
||||
coloredlogs==15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
cryptography==3.3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
dateparser==0.7.6
|
||||
django-cors-headers==3.6.0
|
||||
django-extensions==3.1.0
|
||||
django-filter==2.4.0
|
||||
django-picklefield==3.0.1; python_version >= '3'
|
||||
django-q==1.3.4
|
||||
django==3.1.5
|
||||
djangorestframework==3.12.2
|
||||
filelock==3.0.12
|
||||
fuzzywuzzy==0.18.0
|
||||
gunicorn==20.0.4
|
||||
humanfriendly==9.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
imap-tools==0.34.0
|
||||
img2pdf==0.4.0
|
||||
importlib-metadata==3.4.0; python_version < '3.8'
|
||||
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.0; 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'
|
||||
numpy==1.19.5; python_version >= '3.6'
|
||||
ocrmypdf==11.4.5
|
||||
pathvalidate==2.3.2
|
||||
pdfminer.six==20201018; python_version >= '3.4'
|
||||
pdftotext==2.1.5
|
||||
pikepdf==2.2.5
|
||||
pillow==8.1.0
|
||||
pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
psycopg2-binary==2.8.6
|
||||
pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
python-dateutil==2.8.1
|
||||
python-dotenv==0.15.0
|
||||
python-gnupg==0.4.6
|
||||
python-levenshtein==0.12.0
|
||||
python-magic==0.4.18
|
||||
pytz==2020.5
|
||||
redis==3.5.3
|
||||
regex==2020.11.13
|
||||
reportlab==3.5.59
|
||||
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; python_version >= '3.6'
|
||||
six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
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.56.0
|
||||
typing-extensions==3.7.4.3; python_version < '3.8'
|
||||
tzlocal==2.1
|
||||
urllib3==1.26.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
|
||||
watchdog==1.0.2
|
||||
wcwidth==0.2.5
|
||||
whitenoise==5.2.0
|
||||
whoosh==2.7.4
|
||||
zipp==3.4.0; python_version >= '3.6'
|
@@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Release checklist
|
||||
# - wait for travis build.
|
||||
# adjust src/paperless/version.py
|
||||
# changelog in the documentation
|
||||
# adjust versions in docker/hub/*
|
||||
# If docker-compose was modified: all compose files are the same.
|
||||
|
||||
# Steps:
|
||||
# run release script "dev", push
|
||||
# if it works: new tag, merge into master
|
||||
# on master: make release "lastest", push
|
||||
# on master: make release "version-tag", push
|
||||
# publish release files
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if [ -z "$VERSION" ]
|
||||
then
|
||||
echo "Need a version string."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# source root directory of paperless
|
||||
PAPERLESS_ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
# output directory
|
||||
PAPERLESS_DIST="$PAPERLESS_ROOT/dist"
|
||||
PAPERLESS_DIST_APP="$PAPERLESS_DIST/paperless-ng"
|
||||
PAPERLESS_DIST_DOCKERFILES="$PAPERLESS_DIST/paperless-ng-dockerfiles"
|
||||
|
||||
if [ -d "$PAPERLESS_DIST" ]
|
||||
then
|
||||
echo "Removing $PAPERLESS_DIST"
|
||||
rm "$PAPERLESS_DIST" -r
|
||||
fi
|
||||
|
||||
mkdir "$PAPERLESS_DIST"
|
||||
mkdir "$PAPERLESS_DIST_APP"
|
||||
mkdir "$PAPERLESS_DIST_APP/docker"
|
||||
mkdir "$PAPERLESS_DIST_APP/scripts"
|
||||
mkdir "$PAPERLESS_DIST_DOCKERFILES"
|
||||
|
||||
# setup dependencies.
|
||||
|
||||
cd "$PAPERLESS_ROOT"
|
||||
|
||||
pipenv clean
|
||||
pipenv install --dev
|
||||
pipenv lock --keep-outdated -r > "$PAPERLESS_DIST_APP/requirements.txt"
|
||||
|
||||
# test if the application works.
|
||||
|
||||
cd "$PAPERLESS_ROOT/src"
|
||||
pipenv run pytest --cov
|
||||
pipenv run pycodestyle
|
||||
|
||||
# make the documentation.
|
||||
|
||||
cd "$PAPERLESS_ROOT/docs"
|
||||
make clean html
|
||||
|
||||
# copy stuff into place
|
||||
|
||||
# the application itself
|
||||
|
||||
cp "$PAPERLESS_ROOT/.env" \
|
||||
"$PAPERLESS_ROOT/.dockerignore" \
|
||||
"$PAPERLESS_ROOT/CONTRIBUTING.md" \
|
||||
"$PAPERLESS_ROOT/LICENSE" \
|
||||
"$PAPERLESS_ROOT/Pipfile" \
|
||||
"$PAPERLESS_ROOT/Pipfile.lock" \
|
||||
"$PAPERLESS_ROOT/README.md" "$PAPERLESS_DIST_APP"
|
||||
|
||||
cp "$PAPERLESS_ROOT/paperless.conf.example" "$PAPERLESS_DIST_APP/paperless.conf"
|
||||
|
||||
# copy python source, templates and static files.
|
||||
cd "$PAPERLESS_ROOT"
|
||||
find src -wholename '*/templates/*' -o -wholename '*/static/*' -o -name '*.py' | cpio -pdm "$PAPERLESS_DIST_APP"
|
||||
|
||||
# build the front end.
|
||||
|
||||
cd "$PAPERLESS_ROOT/src-ui"
|
||||
ng build --prod --output-hashing none --sourceMap=false --output-path "$PAPERLESS_DIST_APP/src/documents/static/frontend"
|
||||
|
||||
# documentation
|
||||
cp "$PAPERLESS_ROOT/docs/_build/html/" "$PAPERLESS_DIST_APP/docs" -r
|
||||
|
||||
# docker files for building the image yourself
|
||||
cp "$PAPERLESS_ROOT/docker/local/"* "$PAPERLESS_DIST_APP"
|
||||
cp "$PAPERLESS_ROOT/docker/docker-compose.env" "$PAPERLESS_DIST_APP"
|
||||
|
||||
# docker files for pulling from docker hub
|
||||
cp "$PAPERLESS_ROOT/docker/hub/"* "$PAPERLESS_DIST_DOCKERFILES"
|
||||
cp "$PAPERLESS_ROOT/.env" "$PAPERLESS_DIST_DOCKERFILES"
|
||||
cp "$PAPERLESS_ROOT/docker/docker-compose.env" "$PAPERLESS_DIST_DOCKERFILES"
|
||||
|
||||
# auxiliary files required for the docker image
|
||||
cp "$PAPERLESS_ROOT/docker/docker-entrypoint.sh" "$PAPERLESS_DIST_APP/docker/"
|
||||
cp "$PAPERLESS_ROOT/docker/gunicorn.conf.py" "$PAPERLESS_DIST_APP/docker/"
|
||||
cp "$PAPERLESS_ROOT/docker/imagemagick-policy.xml" "$PAPERLESS_DIST_APP/docker/"
|
||||
cp "$PAPERLESS_ROOT/docker/supervisord.conf" "$PAPERLESS_DIST_APP/docker/"
|
||||
|
||||
# auxiliary files for bare metal installs
|
||||
cp "$PAPERLESS_ROOT/scripts/paperless-webserver.service" "$PAPERLESS_DIST_APP/scripts/"
|
||||
cp "$PAPERLESS_ROOT/scripts/paperless-consumer.service" "$PAPERLESS_DIST_APP/scripts/"
|
||||
cp "$PAPERLESS_ROOT/scripts/paperless-scheduler.service" "$PAPERLESS_DIST_APP/scripts/"
|
||||
|
||||
# try to make the docker build.
|
||||
|
||||
cd "$PAPERLESS_DIST_APP"
|
||||
|
||||
docker build . -t "jonaswinkler/paperless-ng:$VERSION"
|
||||
|
||||
# works. package the app!
|
||||
|
||||
cd "$PAPERLESS_DIST"
|
||||
|
||||
tar -cJf "paperless-ng-$VERSION.tar.xz" paperless-ng/
|
||||
tar -cJf "paperless-ng-$VERSION-dockerfiles.tar.xz" paperless-ng-dockerfiles/
|
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if [ -z "$VERSION" ]
|
||||
then
|
||||
echo "Need a version string."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# source root directory of paperless
|
||||
PAPERLESS_ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
# output directory
|
||||
PAPERLESS_DIST="$PAPERLESS_ROOT/dist"
|
||||
PAPERLESS_DIST_APP="$PAPERLESS_DIST/paperless-ng"
|
||||
|
||||
cd "$PAPERLESS_DIST_APP"
|
||||
|
||||
docker push "jonaswinkler/paperless-ng:$VERSION"
|
@@ -1,2 +1,4 @@
|
||||
docker run -p 5432:5432 -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
|
||||
|
@@ -13,6 +13,14 @@
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"i18n": {
|
||||
"sourceLocale": "en-US",
|
||||
"locales": {
|
||||
"de": "src/locale/messages.de.xlf",
|
||||
"nl-NL": "src/locale/messages.nl_NL.xlf",
|
||||
"fr": "src/locale/messages.fr.xlf"
|
||||
}
|
||||
},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
@@ -23,15 +31,24 @@
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"localize": true,
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest", {
|
||||
"glob": "pdf.worker.min.js",
|
||||
"input": "node_modules/pdfjs-dist/build/",
|
||||
"output": "/assets/js/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
"scripts": [],
|
||||
"allowedCommonJsDependencies": [
|
||||
"ng2-pdf-viewer"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -41,6 +58,7 @@
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputPath": "../src/documents/static/frontend/",
|
||||
"optimization": true,
|
||||
"outputHashing": "none",
|
||||
"sourceMap": false,
|
||||
@@ -61,13 +79,16 @@
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"en-US": {
|
||||
"localize": ["en-US"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "paperless-ui:build"
|
||||
"browserTarget": "paperless-ui:build:en-US"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -90,7 +111,8 @@
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
@@ -127,4 +149,4 @@
|
||||
}
|
||||
},
|
||||
"defaultProject": "paperless-ui"
|
||||
}
|
||||
}
|
||||
|
1692
src-ui/messages.xlf
Normal file
1692
src-ui/messages.xlf
Normal file
File diff suppressed because it is too large
Load Diff
85
src-ui/package-lock.json
generated
85
src-ui/package-lock.json
generated
@@ -331,6 +331,12 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
|
||||
@@ -2056,6 +2062,14 @@
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ng-select/ng-select": {
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-5.0.9.tgz",
|
||||
"integrity": "sha512-YZeSAiS8/Nx/eHZJPmOOYL8YmcvSq+dr1P8WIrsKmRA7mueorBpPc5xlUj+nLQbpLtsiQvdWDQspf/ykOvD/lA==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ngtools/webpack": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.2.0.tgz",
|
||||
@@ -2170,6 +2184,14 @@
|
||||
"pacote": "9.5.12",
|
||||
"semver": "7.3.2",
|
||||
"semver-intersect": "1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/glob": {
|
||||
@@ -2215,6 +2237,11 @@
|
||||
"integrity": "sha512-UV1/ZJMC+HcP902wWdpC43cAcGu0IQk/I5bXjP2aSuCjsk3cE74mDvFrLKga7oDC170ugOAYBwfT4DSQW3akDA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/pdfjs-dist": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/pdfjs-dist/-/pdfjs-dist-2.1.7.tgz",
|
||||
"integrity": "sha512-nQIwcPUhkAIyn7x9NS0lR/qxYfd5unRtfGkMjvpgF4Sh28IXftRymaNmFKTTdejDNY25NDGSIyjwj/BRwAPexg=="
|
||||
},
|
||||
"@types/q": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
|
||||
@@ -3023,6 +3050,16 @@
|
||||
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||
"dev": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"blob": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
|
||||
@@ -5508,6 +5545,13 @@
|
||||
"schema-utils": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -8208,6 +8252,13 @@
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
@@ -8260,6 +8311,23 @@
|
||||
"moment": "2.18.1"
|
||||
}
|
||||
},
|
||||
"ng2-pdf-viewer": {
|
||||
"version": "6.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-6.3.2.tgz",
|
||||
"integrity": "sha512-H2tBhDd+Lq6CUzK2g54HsCcZDR2wTn1sDjYqKY3yF0Ydasl2R5ppCKynZBU/zge4EKvmHglJI120FbQMpJKDYQ==",
|
||||
"requires": {
|
||||
"@types/pdfjs-dist": "^2.1.4",
|
||||
"pdfjs-dist": "^2.4.456",
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ngx-cookie-service": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-10.1.1.tgz",
|
||||
@@ -9270,6 +9338,11 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"pdfjs-dist": {
|
||||
"version": "2.5.207",
|
||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.5.207.tgz",
|
||||
"integrity": "sha512-xGDUhnCYPfHy+unMXCLCJtlpZaaZ17Ew3WIL0tnSgKFUZXHAPD49GO9xScyszSsQMoutNDgRb+rfBXIaX/lJbw=="
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@@ -13228,7 +13301,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
@@ -13832,7 +13909,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
|
@@ -21,8 +21,10 @@
|
||||
"@angular/platform-browser-dynamic": "~10.1.5",
|
||||
"@angular/router": "~10.1.5",
|
||||
"@ng-bootstrap/ng-bootstrap": "^8.0.0",
|
||||
"@ng-select/ng-select": "^5.0.9",
|
||||
"bootstrap": "^4.5.0",
|
||||
"ng-bootstrap": "^1.6.3",
|
||||
"ng2-pdf-viewer": "^6.3.2",
|
||||
"ngx-cookie-service": "^10.1.1",
|
||||
"ngx-file-drop": "^10.0.0",
|
||||
"ngx-infinite-scroll": "^9.1.0",
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -6,9 +7,11 @@ import { Component } from '@angular/core';
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
constructor () {
|
||||
|
||||
constructor (private settings: SettingsService) {
|
||||
let anyWindow = (window as any)
|
||||
anyWindow.pdfWorkerSrc = '/assets/js/pdf.worker.min.js';
|
||||
this.settings.updateDarkModeSettings()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -14,10 +14,9 @@ import { LogsComponent } from './components/manage/logs/logs.component';
|
||||
import { SettingsComponent } from './components/manage/settings/settings.component';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { SafePipe } from './pipes/safe.pipe';
|
||||
import { NotFoundComponent } from './components/not-found/not-found.component';
|
||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component';
|
||||
import { DeleteDialogComponent } from './components/common/delete-dialog/delete-dialog.component';
|
||||
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component';
|
||||
import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component';
|
||||
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';
|
||||
@@ -27,9 +26,13 @@ import { ResultHighlightComponent } from './components/search/result-highlight/r
|
||||
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';
|
||||
import { FilterEditorComponent } from './components/filter-editor/filter-editor.component';
|
||||
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component';
|
||||
import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component';
|
||||
import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
|
||||
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component';
|
||||
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component';
|
||||
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component';
|
||||
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 { SelectComponent } from './components/common/input/select/select.component';
|
||||
@@ -45,9 +48,17 @@ import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-v
|
||||
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component';
|
||||
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component';
|
||||
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component';
|
||||
import { PdfViewerModule } from 'ng2-pdf-viewer';
|
||||
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component';
|
||||
import { YesNoPipe } from './pipes/yes-no.pipe';
|
||||
import { FileSizePipe } from './pipes/file-size.pipe';
|
||||
import { FilterPipe } from './pipes/filter.pipe';
|
||||
import { DocumentTitlePipe } from './pipes/document-title.pipe';
|
||||
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component';
|
||||
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { NumberComponent } from './components/common/input/number/number.component';
|
||||
import { SafePipe } from './pipes/safe.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -60,10 +71,9 @@ import { FileSizePipe } from './pipes/file-size.pipe';
|
||||
DocumentTypeListComponent,
|
||||
LogsComponent,
|
||||
SettingsComponent,
|
||||
SafePipe,
|
||||
NotFoundComponent,
|
||||
CorrespondentEditDialogComponent,
|
||||
DeleteDialogComponent,
|
||||
ConfirmDialogComponent,
|
||||
TagEditDialogComponent,
|
||||
DocumentTypeEditDialogComponent,
|
||||
TagComponent,
|
||||
@@ -73,8 +83,12 @@ import { FileSizePipe } from './pipes/file-size.pipe';
|
||||
AppFrameComponent,
|
||||
ToastsComponent,
|
||||
FilterEditorComponent,
|
||||
FilterableDropdownComponent,
|
||||
ToggleableDropdownButtonComponent,
|
||||
DateDropdownComponent,
|
||||
DocumentCardLargeComponent,
|
||||
DocumentCardSmallComponent,
|
||||
BulkEditorComponent,
|
||||
TextComponent,
|
||||
SelectComponent,
|
||||
CheckComponent,
|
||||
@@ -88,7 +102,13 @@ import { FileSizePipe } from './pipes/file-size.pipe';
|
||||
WidgetFrameComponent,
|
||||
WelcomeWidgetComponent,
|
||||
YesNoPipe,
|
||||
FileSizePipe
|
||||
FileSizePipe,
|
||||
FilterPipe,
|
||||
DocumentTitlePipe,
|
||||
MetadataCollapseComponent,
|
||||
SelectDialogComponent,
|
||||
NumberComponent,
|
||||
SafePipe
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -98,7 +118,9 @@ import { FileSizePipe } from './pipes/file-size.pipe';
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgxFileDropModule,
|
||||
InfiniteScrollModule
|
||||
InfiniteScrollModule,
|
||||
PdfViewerModule,
|
||||
NgSelectModule
|
||||
],
|
||||
providers: [
|
||||
DatePipe,
|
||||
@@ -106,7 +128,9 @@ import { FileSizePipe } from './pipes/file-size.pipe';
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: CsrfInterceptor,
|
||||
multi: true
|
||||
}
|
||||
},
|
||||
FilterPipe,
|
||||
DocumentTitlePipe
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@@ -1,158 +1,177 @@
|
||||
<nav class="navbar navbar-dark sticky-top bg-primary flex-md-nowrap p-0 shadow">
|
||||
<span class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">
|
||||
<img src="assets/logo-dark-notext.svg" height="18px" class="mr-2">
|
||||
Paperless-ng
|
||||
</span>
|
||||
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
|
||||
<button class="navbar-toggler d-md-none collapsed border-0" type="button" data-toggle="collapse"
|
||||
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"
|
||||
(click)="isMenuCollapsed = !isMenuCollapsed">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<form (ngSubmit)="search()" class="w-100 m-1">
|
||||
<input class="form-control form-control-dark" type="text" placeholder="Search" aria-label="Search"
|
||||
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (selectItem)="itemSelected($event)">
|
||||
</form>
|
||||
<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">
|
||||
<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>
|
||||
</a>
|
||||
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 pl-md-4 mr-sm-auto order-3 order-sm-1">
|
||||
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
|
||||
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
|
||||
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (selectItem)="itemSelected($event)" i18n-placeholder>
|
||||
<svg width="1em" height="1em">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#search"/>
|
||||
</svg>
|
||||
</form>
|
||||
</div>
|
||||
<ul ngbNav class="order-sm-3">
|
||||
<li ngbDropdown class="nav-item dropdown">
|
||||
<button class="btn text-light" id="userDropdown" ngbDropdownToggle>
|
||||
<span *ngIf="displayName" class="navbar-text small mr-2 text-light d-none d-sm-inline">
|
||||
{{displayName}}
|
||||
</span>
|
||||
<svg width="1.3em" height="1.3em">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-circle"/>
|
||||
</svg>
|
||||
</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>
|
||||
<div class="dropdown-divider"></div>
|
||||
</div>
|
||||
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()">
|
||||
<svg class="sidebaricon mr-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg><ng-container i18n>Settings</ng-container>
|
||||
</a>
|
||||
<a ngbDropdownItem class="nav-link" href="accounts/logout/">
|
||||
<svg class="sidebaricon mr-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#door-open"/>
|
||||
</svg><ng-container i18n>Logout</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" [ngbCollapse]="isMenuCollapsed">
|
||||
|
||||
<div style="position: absolute; bottom: 0; left: 0;" class="text-muted p-1">
|
||||
{{versionString}}
|
||||
</div>
|
||||
|
||||
<div class="sidebar-sticky pt-3">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#house"/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</svg> <ng-container i18n>Dashboard</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="documents" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#files"/>
|
||||
</svg>
|
||||
Documents
|
||||
</svg> <ng-container i18n>Documents</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='viewConfigService.getSideBarConfigs().length > 0'>
|
||||
<span>Saved views</span>
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.sidebarViews.length > 0'>
|
||||
<ng-container i18n>Saved views</ng-container>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item w-100" *ngFor='let config of viewConfigService.getSideBarConfigs()'>
|
||||
<a class="nav-link text-truncate" routerLink="view/{{config.id}}" routerLinkActive="active" (click)="closeMenu()">
|
||||
<li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews">
|
||||
<a class="nav-link text-truncate" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
||||
</svg>
|
||||
{{config.title}}
|
||||
</svg> {{view.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
|
||||
<span>Open documents</span>
|
||||
<ng-container i18n>Open documents</ng-container>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item w-100" *ngFor='let d of openDocuments'>
|
||||
<a class="nav-link text-truncate" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg>
|
||||
{{d.title}}
|
||||
</svg> {{d.title | documentTitle}}
|
||||
</a>
|
||||
</li>
|
||||
<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"/>
|
||||
</svg>
|
||||
Close all
|
||||
</svg> <ng-container i18n>Close all</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>Manage</span>
|
||||
<ng-container i18n>Manage</ng-container>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person"/>
|
||||
</svg>
|
||||
Correspondents
|
||||
</svg> <ng-container i18n>Correspondents</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
|
||||
</svg>
|
||||
Tags
|
||||
</svg> <ng-container i18n>Tags</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
||||
</svg>
|
||||
Document types
|
||||
</svg> <ng-container i18n>Document types</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
|
||||
</svg>
|
||||
Logs
|
||||
</svg> <ng-container i18n>Logs</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg>
|
||||
Settings
|
||||
</svg> <ng-container i18n>Settings</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="admin/">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#toggles"/>
|
||||
</svg>
|
||||
Admin
|
||||
</svg> <ng-container i18n>Admin</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>Misc</span>
|
||||
<ng-container i18n>Misc</ng-container>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://paperless-ng.readthedocs.io/en/latest/">
|
||||
<a class="nav-link" target="_blank" rel="noopener noreferrer" href="https://paperless-ng.readthedocs.io/en/latest/">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
|
||||
</svg>
|
||||
Documentation
|
||||
</svg> <ng-container i18n>Documentation</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/jonaswinkler/paperless-ng">
|
||||
<a class="nav-link" target="_blank" rel="noopener noreferrer" href="https://github.com/jonaswinkler/paperless-ng">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#link"/>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="accounts/logout/">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#door-open"/>
|
||||
</svg>
|
||||
Logout
|
||||
</svg> <ng-container i18n>GitHub</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@@ -1,36 +1,30 @@
|
||||
|
||||
@import "/src/theme";
|
||||
|
||||
/*
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
.sidebar {
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100; /* Behind the navbar */
|
||||
padding: 48px 0 0; /* Height of navbar */
|
||||
padding: 50px 0 0; /* Height of navbar */
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sidebar {
|
||||
top: 3rem;
|
||||
top: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-sticky {
|
||||
position: relative;
|
||||
top: 0;
|
||||
/* height: calc(100vh - 48px); */
|
||||
height: 100%;
|
||||
padding-top: .5rem;
|
||||
padding-top: 0.5rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||
}
|
||||
|
||||
@supports ((position: -webkit-sticky) or (position: sticky)) {
|
||||
.sidebar-sticky {
|
||||
position: -webkit-sticky;
|
||||
@@ -53,36 +47,85 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover .sidebaricon,
|
||||
.sidebar .nav-link.active .sidebaricon {
|
||||
.sidebar .nav-link.active .sidebaricon,
|
||||
.sidebar .nav-link:hover .sidebaricon {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
.navbar-brand {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.navbar .navbar-toggler {
|
||||
top: .25rem;
|
||||
right: 1rem;
|
||||
.dropdown.show .dropdown-toggle,
|
||||
.dropdown-toggle:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
.dropdown-toggle::after {
|
||||
margin-left: 0.4em;
|
||||
vertical-align: 0.155em;
|
||||
}
|
||||
|
||||
.navbar .dropdown-menu {
|
||||
font-size: 0.875rem; // body size
|
||||
|
||||
a svg {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar .search-form-container {
|
||||
max-width: 550px;
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
left: 0.6rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
padding-left: 1.8rem;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transition: all .3s ease, padding-left 0s ease, background-color 0s ease; // Safari requires all
|
||||
max-width: 600px;
|
||||
min-width: 300px; // 1/2 max
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: #fff;
|
||||
color: #212529;
|
||||
flex-grow: 1;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,12 @@ import { from, Observable, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
||||
import { SearchService } from 'src/app/services/rest/search.service';
|
||||
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component';
|
||||
|
||||
import { Meta } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-app-frame',
|
||||
templateUrl: './app-frame.component.html',
|
||||
@@ -21,10 +23,14 @@ export class AppFrameComponent implements OnInit, OnDestroy {
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private openDocumentsService: OpenDocumentsService,
|
||||
private searchService: SearchService,
|
||||
public viewConfigService: SavedViewConfigService
|
||||
public savedViewService: SavedViewService,
|
||||
private meta: Meta
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
versionString = `${environment.appTitle} ${environment.version}`
|
||||
|
||||
isMenuCollapsed: boolean = true
|
||||
|
||||
closeMenu() {
|
||||
@@ -52,7 +58,7 @@ export class AppFrameComponent implements OnInit, OnDestroy {
|
||||
term.length < 2 ? from([[]]) : this.searchService.autocomplete(term)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
itemSelected(event) {
|
||||
event.preventDefault()
|
||||
let currentSearch: string = this.searchField.value
|
||||
@@ -95,4 +101,17 @@ export class AppFrameComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
// TODO: taken from dashboard component, is this the best way to pass around username?
|
||||
let tagFullName = this.meta.getTag('name=full_name')
|
||||
let tagUsername = this.meta.getTag('name=username')
|
||||
if (tagFullName && tagFullName.content) {
|
||||
return tagFullName.content
|
||||
} else if (tagUsername && tagUsername.content) {
|
||||
return tagUsername.content
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,17 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p *ngIf="messageBold"><b>{{messageBold}}</b></p>
|
||||
<p *ngIf="message">{{message}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" [disabled]="!buttonsEnabled" i18n>Cancel</button>
|
||||
<button type="button" class="btn" [class]="btnClass" (click)="confirmClicked.emit()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
||||
{{btnCaption}}
|
||||
<span *ngIf="!confirmButtonEnabled"> ({{seconds}})</span>
|
||||
</button>
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ConfirmDialogComponent } from './confirm-dialog.component';
|
||||
|
||||
describe('ConfirmDialogComponent', () => {
|
||||
let component: ConfirmDialogComponent;
|
||||
let fixture: ComponentFixture<ConfirmDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ConfirmDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfirmDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,55 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirm-dialog',
|
||||
templateUrl: './confirm-dialog.component.html',
|
||||
styleUrls: ['./confirm-dialog.component.scss']
|
||||
})
|
||||
export class ConfirmDialogComponent implements OnInit {
|
||||
|
||||
constructor(public activeModal: NgbActiveModal) { }
|
||||
|
||||
@Output()
|
||||
public confirmClicked = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
title = $localize`Confirmation`
|
||||
|
||||
@Input()
|
||||
messageBold
|
||||
|
||||
@Input()
|
||||
message
|
||||
|
||||
@Input()
|
||||
btnClass = "btn-primary"
|
||||
|
||||
@Input()
|
||||
btnCaption = $localize`Confirm`
|
||||
|
||||
@Input()
|
||||
buttonsEnabled = true
|
||||
|
||||
confirmButtonEnabled = true
|
||||
seconds = 0
|
||||
|
||||
delayConfirm(seconds: number) {
|
||||
this.confirmButtonEnabled = false
|
||||
this.seconds = seconds
|
||||
setTimeout(() => {
|
||||
if (this.seconds <= 1) {
|
||||
this.confirmButtonEnabled = true
|
||||
} else {
|
||||
this.delayConfirm(seconds - 1)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
cancelClicked() {
|
||||
this.activeModal.close()
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'">
|
||||
{{title}}
|
||||
</button>
|
||||
<div class="dropdown-menu date-dropdown shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button *ngFor="let qf of quickFilters" class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" role="menuitem" (click)="setDateQuickFilter(qf.id)">
|
||||
{{qf.name}}
|
||||
</button>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>After</div>
|
||||
<a *ngIf="dateAfter" class="btn btn-link p-0 m-0" (click)="clearAfter()">
|
||||
<svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
||||
</svg>
|
||||
<small i18n>Clear</small>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="date" class="form-control" id="date_after" [(ngModel)]="dateAfter" (change)="onChangeDebounce()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>Before</div>
|
||||
<a *ngIf="dateBefore" class="btn btn-link p-0 m-0" (click)="clearBefore()">
|
||||
<svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
||||
</svg>
|
||||
<small i18n>Clear</small>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="date" class="form-control" id="date_before" [(ngModel)]="dateBefore" (change)="onChangeDebounce()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,7 @@
|
||||
.date-dropdown {
|
||||
min-width: 250px;
|
||||
|
||||
.btn-link {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
@@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DeleteDialogComponent } from './delete-dialog.component';
|
||||
import { DateDropdownComponent } from './date-dropdown.component';
|
||||
|
||||
describe('DeleteDialogComponent', () => {
|
||||
let component: DeleteDialogComponent;
|
||||
let fixture: ComponentFixture<DeleteDialogComponent>;
|
||||
describe('DateDropdownComponent', () => {
|
||||
let component: DateDropdownComponent;
|
||||
let fixture: ComponentFixture<DateDropdownComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DeleteDialogComponent ]
|
||||
declarations: [ DateDropdownComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DeleteDialogComponent);
|
||||
fixture = TestBed.createComponent(DateDropdownComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@@ -0,0 +1,111 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
export interface DateSelection {
|
||||
before?: string
|
||||
after?: string
|
||||
}
|
||||
|
||||
const LAST_7_DAYS = 0
|
||||
const LAST_MONTH = 1
|
||||
const LAST_3_MONTHS = 2
|
||||
const LAST_YEAR = 3
|
||||
|
||||
@Component({
|
||||
selector: 'app-date-dropdown',
|
||||
templateUrl: './date-dropdown.component.html',
|
||||
styleUrls: ['./date-dropdown.component.scss']
|
||||
})
|
||||
export class DateDropdownComponent implements OnInit, OnDestroy {
|
||||
|
||||
quickFilters = [
|
||||
{id: LAST_7_DAYS, name: $localize`Last 7 days`},
|
||||
{id: LAST_MONTH, name: $localize`Last month`},
|
||||
{id: LAST_3_MONTHS, name: $localize`Last 3 months`},
|
||||
{id: LAST_YEAR, name: $localize`Last year`}
|
||||
]
|
||||
|
||||
@Input()
|
||||
dateBefore: string
|
||||
|
||||
@Output()
|
||||
dateBeforeChange = new EventEmitter<string>()
|
||||
|
||||
@Input()
|
||||
dateAfter: string
|
||||
|
||||
@Output()
|
||||
dateAfterChange = new EventEmitter<string>()
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
@Output()
|
||||
datesSet = new EventEmitter<DateSelection>()
|
||||
|
||||
private datesSetDebounce$ = new Subject()
|
||||
|
||||
private sub: Subscription
|
||||
|
||||
ngOnInit() {
|
||||
this.sub = this.datesSetDebounce$.pipe(
|
||||
debounceTime(400)
|
||||
).subscribe(() => {
|
||||
this.onChange()
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.sub) {
|
||||
this.sub.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
setDateQuickFilter(qf: number) {
|
||||
this.dateBefore = null
|
||||
let date = new Date()
|
||||
switch (qf) {
|
||||
case LAST_7_DAYS:
|
||||
date.setDate(date.getDate() - 7)
|
||||
break;
|
||||
|
||||
case LAST_MONTH:
|
||||
date.setMonth(date.getMonth() - 1)
|
||||
break;
|
||||
|
||||
case LAST_3_MONTHS:
|
||||
date.setMonth(date.getMonth() - 3)
|
||||
break
|
||||
|
||||
case LAST_YEAR:
|
||||
date.setFullYear(date.getFullYear() - 1)
|
||||
break
|
||||
|
||||
}
|
||||
this.dateAfter = formatDate(date, 'yyyy-MM-dd', "en-us", "UTC")
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.dateAfterChange.emit(this.dateAfter)
|
||||
this.dateBeforeChange.emit(this.dateBefore)
|
||||
this.datesSet.emit({after: this.dateAfter, before: this.dateBefore})
|
||||
}
|
||||
|
||||
onChangeDebounce() {
|
||||
this.datesSetDebounce$.next({after: this.dateAfter, before: this.dateBefore})
|
||||
}
|
||||
|
||||
clearBefore() {
|
||||
this.dateBefore = null
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
clearAfter() {
|
||||
this.dateAfter = null
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><b>{{message}}</b></p>
|
||||
<p *ngIf="message2">{{message2}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deleteClicked.emit()">Delete</button>
|
||||
</div>
|
@@ -1,31 +0,0 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-dialog',
|
||||
templateUrl: './delete-dialog.component.html',
|
||||
styleUrls: ['./delete-dialog.component.scss']
|
||||
})
|
||||
export class DeleteDialogComponent implements OnInit {
|
||||
|
||||
constructor(public activeModal: NgbActiveModal) { }
|
||||
|
||||
@Output()
|
||||
public deleteClicked = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
title = "Delete confirmation"
|
||||
|
||||
@Input()
|
||||
message = "Do you really want to delete this?"
|
||||
|
||||
@Input()
|
||||
message2
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
cancelClicked() {
|
||||
this.activeModal.close()
|
||||
}
|
||||
}
|
@@ -2,10 +2,11 @@ import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Observable } from 'rxjs';
|
||||
import { MATCHING_ALGORITHMS } from 'src/app/data/matching-model';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model';
|
||||
import { ObjectWithId } from 'src/app/data/object-with-id';
|
||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Directive()
|
||||
export abstract class EditDialogComponent<T extends ObjectWithId> implements OnInit {
|
||||
@@ -13,8 +14,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
|
||||
constructor(
|
||||
private service: AbstractPaperlessService<T>,
|
||||
private activeModal: NgbActiveModal,
|
||||
private toastService: ToastService,
|
||||
private entityName: string) { }
|
||||
private toastService: ToastService) { }
|
||||
|
||||
@Input()
|
||||
dialogMode: string = 'create'
|
||||
@@ -25,6 +25,10 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
|
||||
@Output()
|
||||
success = new EventEmitter()
|
||||
|
||||
networkActive = false
|
||||
|
||||
error = null
|
||||
|
||||
abstract getForm(): FormGroup
|
||||
|
||||
objectForm: FormGroup = this.getForm()
|
||||
@@ -35,12 +39,24 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
|
||||
}
|
||||
}
|
||||
|
||||
getCreateTitle() {
|
||||
return $localize`Create new item`
|
||||
}
|
||||
|
||||
getEditTitle() {
|
||||
return $localize`Edit item`
|
||||
}
|
||||
|
||||
getSaveErrorMessage(error: string) {
|
||||
return $localize`Could not save element: ${error}`
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
switch (this.dialogMode) {
|
||||
case 'create':
|
||||
return "Create new " + this.entityName
|
||||
return this.getCreateTitle()
|
||||
case 'edit':
|
||||
return "Edit " + this.entityName
|
||||
return this.getEditTitle()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -50,6 +66,10 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
|
||||
return MATCHING_ALGORITHMS
|
||||
}
|
||||
|
||||
get patternRequired(): boolean {
|
||||
return this.objectForm?.value.matching_algorithm !== MATCH_AUTO
|
||||
}
|
||||
|
||||
save() {
|
||||
var newObject = Object.assign(Object.assign({}, this.object), this.objectForm.value)
|
||||
var serverResponse: Observable<T>
|
||||
@@ -62,11 +82,13 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.networkActive = true
|
||||
serverResponse.subscribe(result => {
|
||||
this.activeModal.close()
|
||||
this.success.emit(result)
|
||||
}, error => {
|
||||
this.toastService.showToast(Toast.makeError(`Could not save ${this.entityName}: ${error.error.name}`))
|
||||
this.error = error.error
|
||||
this.networkActive = false
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,33 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
<ng-container *ngIf="!editing && selectionModel.selectionSize() > 0">
|
||||
<div class="badge bg-secondary text-light rounded-pill badge-corner">
|
||||
{{selectionModel.selectionSize()}}
|
||||
</div>
|
||||
</ng-container>
|
||||
</button>
|
||||
<div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item">
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="selectionModel.items" class="items">
|
||||
<ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText">
|
||||
<app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)"></app-toggleable-dropdown-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!selectionModel.isDirty()">
|
||||
<small class="ml-1" [ngClass]="{'font-weight-bold': selectionModel.isDirty()}" i18n>Apply</small>
|
||||
<svg width="1.5em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,14 @@
|
||||
.badge-corner {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
min-width: 250px;
|
||||
|
||||
.items {
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FilterableDropodownComponent } from './filterable-dropdown.component';
|
||||
|
||||
describe('FilterableDropodownComponent', () => {
|
||||
let component: FilterableDropodownComponent;
|
||||
let fixture: ComponentFixture<FilterableDropodownComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ FilterableDropodownComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterableDropodownComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,272 @@
|
||||
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core';
|
||||
import { FilterPipe } from 'src/app/pipes/filter.pipe';
|
||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
|
||||
import { MatchingModel } from 'src/app/data/matching-model';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export interface ChangedItems {
|
||||
itemsToAdd: MatchingModel[],
|
||||
itemsToRemove: MatchingModel[]
|
||||
}
|
||||
|
||||
export class FilterableDropdownSelectionModel {
|
||||
|
||||
changed = new Subject<FilterableDropdownSelectionModel>()
|
||||
|
||||
multiple = false
|
||||
|
||||
items: MatchingModel[] = []
|
||||
|
||||
get itemsSorted(): MatchingModel[] {
|
||||
// TODO: this is getting called very often
|
||||
return this.items.sort((a,b) => {
|
||||
if (a.id == null && b.id != null) {
|
||||
return -1
|
||||
} else if (a.id != null && b.id == null) {
|
||||
return 1
|
||||
} else if (this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && this.getNonTemporary(b.id) != ToggleableItemState.NotSelected) {
|
||||
return 1
|
||||
} else if (this.getNonTemporary(a.id) != ToggleableItemState.NotSelected && this.getNonTemporary(b.id) == ToggleableItemState.NotSelected) {
|
||||
return -1
|
||||
} else {
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private selectionStates = new Map<number, ToggleableItemState>()
|
||||
|
||||
private temporarySelectionStates = new Map<number, ToggleableItemState>()
|
||||
|
||||
getSelectedItems() {
|
||||
return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected)
|
||||
}
|
||||
|
||||
set(id: number, state: ToggleableItemState, fireEvent = true) {
|
||||
if (state == ToggleableItemState.NotSelected) {
|
||||
this.temporarySelectionStates.delete(id)
|
||||
} else {
|
||||
this.temporarySelectionStates.set(id, state)
|
||||
}
|
||||
if (fireEvent) {
|
||||
this.changed.next(this)
|
||||
}
|
||||
}
|
||||
|
||||
toggle(id: number, fireEvent = true) {
|
||||
let state = this.temporarySelectionStates.get(id)
|
||||
if (state == null || state != ToggleableItemState.Selected) {
|
||||
this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
|
||||
} else if (state == ToggleableItemState.Selected) {
|
||||
this.temporarySelectionStates.delete(id)
|
||||
}
|
||||
|
||||
if (!this.multiple) {
|
||||
for (let key of this.temporarySelectionStates.keys()) {
|
||||
if (key != id) {
|
||||
this.temporarySelectionStates.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
for (let key of this.temporarySelectionStates.keys()) {
|
||||
if (key) {
|
||||
this.temporarySelectionStates.delete(key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.temporarySelectionStates.delete(null)
|
||||
}
|
||||
|
||||
if (fireEvent) {
|
||||
this.changed.next(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private getNonTemporary(id: number) {
|
||||
return this.selectionStates.get(id) || ToggleableItemState.NotSelected
|
||||
}
|
||||
|
||||
get(id: number) {
|
||||
return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
|
||||
}
|
||||
|
||||
selectionSize() {
|
||||
return this.getSelectedItems().length
|
||||
}
|
||||
|
||||
clear(fireEvent = true) {
|
||||
this.temporarySelectionStates.clear()
|
||||
if (fireEvent) {
|
||||
this.changed.next(this)
|
||||
}
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
if (!Array.from(this.temporarySelectionStates.keys()).every(id => this.temporarySelectionStates.get(id) == this.selectionStates.get(id))) {
|
||||
return true
|
||||
} else if (!Array.from(this.selectionStates.keys()).every(id => this.selectionStates.get(id) == this.temporarySelectionStates.get(id))) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
isNoneSelected() {
|
||||
return this.selectionSize() == 1 && this.get(null) == ToggleableItemState.Selected
|
||||
}
|
||||
|
||||
init(map) {
|
||||
this.temporarySelectionStates = map
|
||||
this.apply()
|
||||
}
|
||||
|
||||
apply() {
|
||||
this.selectionStates.clear()
|
||||
this.temporarySelectionStates.forEach((value, key) => {
|
||||
this.selectionStates.set(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.temporarySelectionStates.clear()
|
||||
this.selectionStates.forEach((value, key) => {
|
||||
this.temporarySelectionStates.set(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
diff(): ChangedItems {
|
||||
return {
|
||||
itemsToAdd: this.items.filter(item => this.temporarySelectionStates.get(item.id) == ToggleableItemState.Selected && this.selectionStates.get(item.id) != ToggleableItemState.Selected),
|
||||
itemsToRemove: this.items.filter(item => !this.temporarySelectionStates.has(item.id) && this.selectionStates.has(item.id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-filterable-dropdown',
|
||||
templateUrl: './filterable-dropdown.component.html',
|
||||
styleUrls: ['./filterable-dropdown.component.scss']
|
||||
})
|
||||
export class FilterableDropdownComponent {
|
||||
|
||||
@ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
|
||||
@ViewChild('dropdown') dropdown: NgbDropdown
|
||||
|
||||
filterText: string
|
||||
|
||||
@Input()
|
||||
set items(items: MatchingModel[]) {
|
||||
if (items) {
|
||||
this._selectionModel.items = Array.from(items)
|
||||
this._selectionModel.items.unshift({
|
||||
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
|
||||
id: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get items(): MatchingModel[] {
|
||||
return this._selectionModel.items
|
||||
}
|
||||
|
||||
_selectionModel = new FilterableDropdownSelectionModel()
|
||||
|
||||
@Input()
|
||||
set selectionModel(model: FilterableDropdownSelectionModel) {
|
||||
if (this.selectionModel) {
|
||||
this.selectionModel.changed.complete()
|
||||
model.items = this.selectionModel.items
|
||||
model.multiple = this.selectionModel.multiple
|
||||
}
|
||||
model.changed.subscribe(updatedModel => {
|
||||
this.selectionModelChange.next(updatedModel)
|
||||
})
|
||||
this._selectionModel = model
|
||||
}
|
||||
|
||||
get selectionModel(): FilterableDropdownSelectionModel {
|
||||
return this._selectionModel
|
||||
}
|
||||
|
||||
@Output()
|
||||
selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>()
|
||||
|
||||
@Input()
|
||||
set multiple(value: boolean) {
|
||||
this.selectionModel.multiple = value
|
||||
}
|
||||
|
||||
get multiple() {
|
||||
return this.selectionModel.multiple
|
||||
}
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
@Input()
|
||||
filterPlaceholder: string = ""
|
||||
|
||||
@Input()
|
||||
icon: string
|
||||
|
||||
@Input()
|
||||
allowSelectNone: boolean = false
|
||||
|
||||
@Input()
|
||||
editing = false
|
||||
|
||||
@Input()
|
||||
applyOnClose = false
|
||||
|
||||
@Output()
|
||||
apply = new EventEmitter<ChangedItems>()
|
||||
|
||||
@Output()
|
||||
open = new EventEmitter()
|
||||
|
||||
constructor(private filterPipe: FilterPipe) {
|
||||
this.selectionModel = new FilterableDropdownSelectionModel()
|
||||
}
|
||||
|
||||
applyClicked() {
|
||||
if (this.selectionModel.isDirty()) {
|
||||
this.dropdown.close()
|
||||
if (!this.applyOnClose) {
|
||||
this.apply.emit(this.selectionModel.diff())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropdownOpenChange(open: boolean): void {
|
||||
if (open) {
|
||||
setTimeout(() => {
|
||||
this.listFilterTextInput.nativeElement.focus();
|
||||
}, 0)
|
||||
if (this.editing) {
|
||||
this.selectionModel.reset()
|
||||
}
|
||||
this.open.next()
|
||||
} else {
|
||||
this.filterText = ''
|
||||
if (this.applyOnClose && this.selectionModel.isDirty()) {
|
||||
this.apply.emit(this.selectionModel.diff())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listFilterEnter(): void {
|
||||
let filtered = this.filterPipe.transform(this.items, this.filterText)
|
||||
if (filtered.length == 1) {
|
||||
this.selectionModel.toggle(filtered[0].id)
|
||||
if (this.editing) {
|
||||
this.applyClicked()
|
||||
} else {
|
||||
this.dropdown.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-left-0 border-right-0 border-bottom" role="menuitem" (click)="toggleItem()">
|
||||
<div class="selected-icon mr-1">
|
||||
<ng-container *ngIf="isChecked()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
|
||||
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isPartiallyChecked()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-dash" viewBox="0 0 16 16">
|
||||
<path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
<div class="mr-1">
|
||||
<app-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag>
|
||||
<ng-template #displayName><small>{{item.name}}</small></ng-template>
|
||||
</div>
|
||||
<div class="badge badge-light rounded-pill ml-auto mr-1">{{item.document_count}}</div>
|
||||
</button>
|
@@ -0,0 +1,4 @@
|
||||
.selected-icon {
|
||||
min-width: 1em;
|
||||
min-height: 1em;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component';
|
||||
|
||||
describe('ToggleableDropdownButtonComponent', () => {
|
||||
let component: ToggleableDropdownButtonComponent;
|
||||
let fixture: ComponentFixture<ToggleableDropdownButtonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ToggleableDropdownButtonComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ToggleableDropdownButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,51 @@
|
||||
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
|
||||
import { MatchingModel } from 'src/app/data/matching-model';
|
||||
|
||||
export interface ToggleableItem {
|
||||
item: MatchingModel,
|
||||
state: ToggleableItemState,
|
||||
count: number
|
||||
}
|
||||
|
||||
export enum ToggleableItemState {
|
||||
NotSelected = 0,
|
||||
Selected = 1,
|
||||
PartiallySelected = 2
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-toggleable-dropdown-button',
|
||||
templateUrl: './toggleable-dropdown-button.component.html',
|
||||
styleUrls: ['./toggleable-dropdown-button.component.scss']
|
||||
})
|
||||
export class ToggleableDropdownButtonComponent {
|
||||
|
||||
@Input()
|
||||
item: MatchingModel
|
||||
|
||||
@Input()
|
||||
state: ToggleableItemState
|
||||
|
||||
@Input()
|
||||
count: number
|
||||
|
||||
@Output()
|
||||
toggle = new EventEmitter()
|
||||
|
||||
get isTag(): boolean {
|
||||
return 'is_inbox_tag' in this.item
|
||||
}
|
||||
|
||||
toggleItem(): void {
|
||||
this.toggle.emit()
|
||||
}
|
||||
|
||||
isChecked() {
|
||||
return this.state == ToggleableItemState.Selected
|
||||
}
|
||||
|
||||
isPartiallyChecked() {
|
||||
return this.state == ToggleableItemState.PartiallySelected
|
||||
}
|
||||
|
||||
}
|
@@ -1,10 +1,13 @@
|
||||
import { Component, Directive, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor } from '@angular/forms';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Directive()
|
||||
export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
||||
|
||||
@ViewChild("inputField")
|
||||
inputField: ElementRef
|
||||
|
||||
constructor() { }
|
||||
|
||||
onChange = (newValue: T) => {};
|
||||
@@ -24,12 +27,21 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.inputField && this.inputField.nativeElement) {
|
||||
this.inputField.nativeElement.focus()
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
@Input()
|
||||
disabled = false;
|
||||
|
||||
@Input()
|
||||
error: string
|
||||
|
||||
value: T
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
|
@@ -0,0 +1,14 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NumberComponent } from './number.component';
|
||||
|
||||
describe('NumberComponent', () => {
|
||||
let component: NumberComponent;
|
||||
let fixture: ComponentFixture<NumberComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ NumberComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NumberComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,39 @@
|
||||
import { Component, forwardRef } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type';
|
||||
import { DocumentService } from 'src/app/services/rest/document.service';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NumberComponent),
|
||||
multi: true
|
||||
}],
|
||||
selector: 'app-input-number',
|
||||
templateUrl: './number.component.html',
|
||||
styleUrls: ['./number.component.scss']
|
||||
})
|
||||
export class NumberComponent extends AbstractInputComponent<number> {
|
||||
|
||||
constructor(private documentService: DocumentService) {
|
||||
super()
|
||||
}
|
||||
|
||||
nextAsn() {
|
||||
if (this.value) {
|
||||
return
|
||||
}
|
||||
this.documentService.listFiltered(1, 1, "archive_serial_number", true, [{rule_type: FILTER_ASN_ISNULL, value: "false"}]).subscribe(
|
||||
results => {
|
||||
if (results.count > 0) {
|
||||
this.value = results.results[0].archive_serial_number + 1
|
||||
} else {
|
||||
this.value = 1
|
||||
}
|
||||
this.onChange(this.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +1,18 @@
|
||||
<div class="form-group">
|
||||
<div class="form-group paperless-input-select">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<div [class.input-group]="showPlusButton()">
|
||||
<select class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()"
|
||||
[disabled]="disabled" [style.color]="textColor" [style.background]="backgroundColor">
|
||||
<option *ngIf="allowNull" [ngValue]="null" class="form-control">---</option>
|
||||
<option *ngFor="let i of items" [ngValue]="i.id" class="form-control">{{i.name}}</option>
|
||||
</select>
|
||||
<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">
|
||||
@@ -15,4 +22,4 @@
|
||||
</div>
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1 @@
|
||||
// styles for ng-select child are in styles.scss
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
|
@@ -1,30 +1,43 @@
|
||||
<div class="form-group">
|
||||
<label for="exampleFormControlTextarea1">Tags</label>
|
||||
<div class="form-group paperless-input-select paperless-input-tags">
|
||||
<label for="tags" i18n>Tags</label>
|
||||
|
||||
<div class="input-group">
|
||||
<div class="form-control tags-form-control" id="tags">
|
||||
<app-tag class="mr-2" *ngFor="let id of displayValue" [tag]="getTag(id)" (click)="removeTag(id)"></app-tag>
|
||||
</div>
|
||||
<div class="input-group flex-nowrap">
|
||||
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue"
|
||||
[multiple]="true"
|
||||
[closeOnSelect]="false"
|
||||
[clearSearchOnAdd]="true"
|
||||
[disabled]="disabled"
|
||||
[hideSelected]="true"
|
||||
(change)="ngSelectChange()">
|
||||
|
||||
<div class="input-group-append" ngbDropdown placement="top-right">
|
||||
<button class="btn btn-outline-secondary" type="button" ngbDropdownToggle></button>
|
||||
<div ngbDropdownMenu class="scrollable-menu shadow">
|
||||
<button type="button" *ngFor="let tag of tags" ngbDropdownItem (click)="addTag(tag.id)">
|
||||
<app-tag [tag]="tag"></app-tag>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template ng-label-tmp let-item="item">
|
||||
<span class="tag-wrap tag-wrap-delete" (click)="removeTag(item.id)">
|
||||
<svg width="1.2em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<app-tag style="background-color: none;" [tag]="getTag(item.id)"></app-tag>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
||||
<div class="tag-wrap">
|
||||
<div class="selected-icon d-inline-block mr-1">
|
||||
<svg *ngIf="displayValue.includes(item.id)" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<app-tag class="mr-2" [tag]="getTag(item.id)"></app-tag>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
|
||||
<div class="input-group-append">
|
||||
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="createTag()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<small class="form-text text-muted" *ngIf="hint">{{hint}}</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,10 +1,12 @@
|
||||
.tags-form-control {
|
||||
height: auto;
|
||||
.selected-icon {
|
||||
min-width: 1em;
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
.tag-wrap {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.scrollable-menu {
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.tag-wrap-delete {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import { ThrowStmt } from '@angular/compiler';
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
||||
import { TagService } from 'src/app/services/rest/tag.service';
|
||||
@@ -23,7 +21,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
|
||||
onChange = (newValue: number[]) => {};
|
||||
|
||||
|
||||
onTouched = () => {};
|
||||
|
||||
writeValue(newValue: number[]): void {
|
||||
@@ -68,29 +66,28 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
removeTag(id) {
|
||||
let index = this.displayValue.indexOf(id)
|
||||
if (index > -1) {
|
||||
this.displayValue.splice(index, 1)
|
||||
let oldValue = this.displayValue
|
||||
oldValue.splice(index, 1)
|
||||
this.displayValue = [...oldValue]
|
||||
this.onChange(this.displayValue)
|
||||
}
|
||||
}
|
||||
|
||||
addTag(id) {
|
||||
let index = this.displayValue.indexOf(id)
|
||||
if (index == -1) {
|
||||
this.displayValue.push(id)
|
||||
this.onChange(this.displayValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
createTag() {
|
||||
var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
|
||||
modal.componentInstance.dialogMode = 'create'
|
||||
modal.componentInstance.success.subscribe(newTag => {
|
||||
this.tagService.listAll().subscribe(tags => {
|
||||
this.tags = tags.results
|
||||
this.addTag(newTag.id)
|
||||
this.displayValue = [...this.displayValue, newTag.id]
|
||||
this.onChange(this.displayValue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
ngSelectChange() {
|
||||
this.value = this.displayValue
|
||||
this.onChange(this.displayValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<input type="text" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
|
||||
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
@@ -1,9 +1,9 @@
|
||||
<div class="row pt-3 pb-1 mb-3 border-bottom align-items-center" >
|
||||
<div class="row pt-3 pb-3 pb-md-1 mb-3 border-bottom align-items-center">
|
||||
<div class="col-md text-truncate">
|
||||
<p class="h2 text-truncate" style="line-height: 1.4">{{title}}</p>
|
||||
<p *ngIf="subTitle" class="h5 text-truncate" style="line-height: 1.4">{{subTitle}}</p>
|
||||
</div>
|
||||
<div class="btn-toolbar col-auto">
|
||||
<div class="btn-toolbar col col-md-auto">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,21 +1,29 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page-header',
|
||||
templateUrl: './page-header.component.html',
|
||||
styleUrls: ['./page-header.component.scss']
|
||||
})
|
||||
export class PageHeaderComponent implements OnInit {
|
||||
export class PageHeaderComponent {
|
||||
|
||||
constructor() { }
|
||||
constructor(private titleService: Title) { }
|
||||
|
||||
_title = ""
|
||||
|
||||
@Input()
|
||||
title: string = ""
|
||||
set title(title: string) {
|
||||
this._title = title
|
||||
this.titleService.setTitle(`${this.title} - ${environment.appTitle}`)
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this._title
|
||||
}
|
||||
|
||||
@Input()
|
||||
subTitle: string = ""
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,15 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<app-input-select [items]="objects" [title]="message" [(ngModel)]="selected"></app-input-select>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" i18n>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="selectClicked.emit(selected)" i18n>Select</button>
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SelectDialogComponent } from './select-dialog.component';
|
||||
|
||||
describe('SelectDialogComponent', () => {
|
||||
let component: SelectDialogComponent;
|
||||
let fixture: ComponentFixture<SelectDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SelectDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SelectDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,34 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ObjectWithId } from 'src/app/data/object-with-id';
|
||||
|
||||
@Component({
|
||||
selector: 'app-select-dialog',
|
||||
templateUrl: './select-dialog.component.html',
|
||||
styleUrls: ['./select-dialog.component.scss']
|
||||
})
|
||||
|
||||
export class SelectDialogComponent implements OnInit {
|
||||
constructor(public activeModal: NgbActiveModal) { }
|
||||
|
||||
@Output()
|
||||
public selectClicked = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
title = $localize`Select`
|
||||
|
||||
@Input()
|
||||
message = $localize`Please select an object`
|
||||
|
||||
@Input()
|
||||
objects: ObjectWithId[] = []
|
||||
|
||||
selected: number
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
cancelClicked() {
|
||||
this.activeModal.close()
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
|
||||
|
||||
@Component({
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { Meta } from '@angular/platform-browser';
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -12,15 +12,36 @@ import { environment } from 'src/environments/environment';
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
public savedViewConfigService: SavedViewConfigService,
|
||||
private titleService: Title) { }
|
||||
private savedViewService: SavedViewService,
|
||||
private meta: Meta
|
||||
) { }
|
||||
|
||||
get displayName() {
|
||||
let tagFullName = this.meta.getTag('name=full_name')
|
||||
let tagUsername = this.meta.getTag('name=username')
|
||||
if (tagFullName && tagFullName.content) {
|
||||
return tagFullName.content
|
||||
} else if (tagUsername && tagUsername.content) {
|
||||
return tagUsername.content
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
savedViews = []
|
||||
get subtitle() {
|
||||
if (this.displayName) {
|
||||
return $localize`Hello ${this.displayName}, welcome to Paperless-ng!`
|
||||
} else {
|
||||
return $localize`Welcome to Paperless-ng!`
|
||||
}
|
||||
}
|
||||
|
||||
savedViews: PaperlessSavedView[] = []
|
||||
|
||||
ngOnInit(): void {
|
||||
this.savedViews = this.savedViewConfigService.getDashboardConfigs()
|
||||
this.titleService.setTitle(`Dashboard - ${environment.appTitle}`)
|
||||
this.savedViewService.listAll().subscribe(results => {
|
||||
this.savedViews = results.results.filter(savedView => savedView.show_on_dashboard)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
<app-widget-frame [title]="savedView.title">
|
||||
<app-widget-frame [title]="savedView.name">
|
||||
|
||||
<a header-buttons [routerLink]="" (click)="showAll()">Show all</a>
|
||||
<a header-buttons [routerLink]="" (click)="showAll()" i18n>Show all</a>
|
||||
|
||||
|
||||
<table content class="table table-sm table-hover table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Created</th>
|
||||
<th scope="col">Title</th>
|
||||
<th i18n>Created</th>
|
||||
<th scope="col" i18n>Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let doc of documents" routerLink="/documents/{{doc.id}}">
|
||||
<td>{{doc.created | date}}</td>
|
||||
<td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ml-1"></app-tag>
|
||||
<td>{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ml-1"></app-tag>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -0,0 +1,8 @@
|
||||
table {
|
||||
overflow-wrap: anywhere;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
th:first-child {
|
||||
width: 25%;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user