mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-05-23 12:58:18 -05:00
Merge branch 'dev' into feature-notification-wf-action
This commit is contained in:
commit
e68e8bb9b9
2
Pipfile
2
Pipfile
@ -23,7 +23,7 @@ djangorestframework-guardian = "*"
|
|||||||
drf-writable-nested = "*"
|
drf-writable-nested = "*"
|
||||||
bleach = "*"
|
bleach = "*"
|
||||||
celery = {extras = ["redis"], version = "*"}
|
celery = {extras = ["redis"], version = "*"}
|
||||||
channels = "~=4.1"
|
channels = "~=4.2"
|
||||||
channels-redis = "*"
|
channels-redis = "*"
|
||||||
concurrent-log-handler = "*"
|
concurrent-log-handler = "*"
|
||||||
filelock = "*"
|
filelock = "*"
|
||||||
|
633
Pipfile.lock
generated
633
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "584249cbeaf29659c975000b5e02b12e45d768d795e4a8ac36118e73bd7c0b8a"
|
"sha256": "a194c6834fba6a14712ba36eb0b896f18d7ef4393523e5d55ccb103104e99ddb"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
@ -282,27 +282,27 @@
|
|||||||
"sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87",
|
"sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87",
|
||||||
"sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"
|
"sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "platform_python_implementation != 'PyPy'",
|
||||||
"version": "==1.17.1"
|
"version": "==1.17.1"
|
||||||
},
|
},
|
||||||
"channels": {
|
"channels": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a3c4419307f582c3f71d67bfb6eff748ae819c2f360b9b141694d84f242baa48",
|
"sha256:6b75bc8d6888fb7236e7e7bf1948520b72d296ad08216a242fc56b1db0ffde1a",
|
||||||
"sha256:e0ed375719f5c1851861f05ed4ce78b0166f9245ca0ecd836cb77d4bb531489d"
|
"sha256:d9e707487431ba5dbce9af982970dab3b0efd786580fadb99e45dca5e39fdd59"
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==4.1.0"
|
|
||||||
},
|
|
||||||
"channels-redis": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:01c26c4d5d3a203f104bba9e5585c0305a70df390d21792386586068162027fd",
|
|
||||||
"sha256:2c5b944a39bd984b72aa8005a3ae11637bf29b5092adeb91c9aad4ab819a8ac4"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.2.0"
|
"version": "==4.2.0"
|
||||||
},
|
},
|
||||||
|
"channels-redis": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2ca33105b3a04b5a327a9c47dd762b546f30b76a0cd3f3f593a23d91d346b6f4",
|
||||||
|
"sha256:8375e81493e684792efe6e6eca60ef3d7782ef76c6664057d2e5c31e80d636dd"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==4.2.1"
|
||||||
|
},
|
||||||
"charset-normalizer": {
|
"charset-normalizer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621",
|
"sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621",
|
||||||
@ -456,36 +456,38 @@
|
|||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362",
|
"sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7",
|
||||||
"sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4",
|
"sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731",
|
||||||
"sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa",
|
"sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b",
|
||||||
"sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83",
|
"sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc",
|
||||||
"sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff",
|
"sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543",
|
||||||
"sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805",
|
"sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385",
|
||||||
"sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6",
|
"sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c",
|
||||||
"sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664",
|
"sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591",
|
||||||
"sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08",
|
"sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede",
|
||||||
"sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e",
|
"sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb",
|
||||||
"sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18",
|
"sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f",
|
||||||
"sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f",
|
"sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123",
|
||||||
"sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73",
|
"sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c",
|
||||||
"sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5",
|
"sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba",
|
||||||
"sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984",
|
"sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c",
|
||||||
"sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd",
|
"sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285",
|
||||||
"sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3",
|
"sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd",
|
||||||
"sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e",
|
"sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092",
|
||||||
"sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405",
|
"sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa",
|
||||||
"sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2",
|
"sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289",
|
||||||
"sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c",
|
"sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02",
|
||||||
"sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995",
|
"sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64",
|
||||||
"sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73",
|
"sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053",
|
||||||
"sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16",
|
"sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417",
|
||||||
"sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7",
|
"sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e",
|
||||||
"sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd",
|
"sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e",
|
||||||
"sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"
|
"sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7",
|
||||||
|
"sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756",
|
||||||
|
"sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
|
||||||
"version": "==43.0.3"
|
"version": "==44.0.0"
|
||||||
},
|
},
|
||||||
"dateparser": {
|
"dateparser": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -498,11 +500,11 @@
|
|||||||
},
|
},
|
||||||
"deprecated": {
|
"deprecated": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c",
|
"sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320",
|
||||||
"sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"
|
"sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.2.14"
|
"version": "==1.2.15"
|
||||||
},
|
},
|
||||||
"deprecation": {
|
"deprecation": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -526,10 +528,11 @@
|
|||||||
"socialaccount"
|
"socialaccount"
|
||||||
],
|
],
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0a3d7baf7beefd6fe8027316302c26ece7433cf4331a3b245d15fc9a7be68b6f"
|
"sha256:92e0242724af03458b05b88c5fa798b01112ab22a86d873a8a9fd8f0ec57bbbf"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==65.2.0"
|
"version": "==65.3.0"
|
||||||
},
|
},
|
||||||
"django-auditlog": {
|
"django-auditlog": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1272,18 +1275,18 @@
|
|||||||
},
|
},
|
||||||
"mysqlclient": {
|
"mysqlclient": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1d2e2ca0fe8405d8d6464edd01bf059951279e4bc27284d39341bd4737b2bc64",
|
"sha256:3da70a07753ba6be881f7d75e795e254f6a0c12795778034acc69769b0649d37",
|
||||||
"sha256:3f9625bea2b9bcde0ace76b32708762d44597491092c819fd1bff5b4e27f709b",
|
"sha256:43c5b30be0675080b9c815f457d73397f0442173e7be83d089b126835e2617ae",
|
||||||
"sha256:8012c633aab8c91ea8172ac479807135b171501b9cad1a7cd9b58c4dc8dcdab5",
|
"sha256:794857bce4f9a1903a99786dd29ad7887f45a870b3d11585b8c51c4a753c4174",
|
||||||
"sha256:add8643c32f738014d252d2bdebb478623b04802e8396d5903905db36474d3ff",
|
"sha256:b0a5cddf1d3488b254605041070086cac743401d876a659a72d706a0d89c8ebb",
|
||||||
"sha256:aee14f1872114865679fcb09aac3772de4595fa7dcf2f83a4c7afee15e508854",
|
"sha256:c0b46d9b78b461dbb62482089ca8040fa916595b1b30f831ebbd1b0a82b43d53",
|
||||||
"sha256:b54511648c1455b43ac28f8b4c1f732c5b0c343e87f7a3bd6fc9f9fe0f91934e",
|
"sha256:e940b41d85dfd7b190fa47d52f525f878cfa203d4653bf6a35b271b3c3be125b",
|
||||||
"sha256:b78438314199504c64f69e1e3521f2c9b419f19fcd85158b44c997b64409a6af",
|
"sha256:e94a92858203d97fd584bdb6d7ee8c56f2590db8d77fd44215c0dcf5e739bc37",
|
||||||
"sha256:e871ede4261d0d42b8ed20a2459db411c7deafedd8e77b7e4ba760be4a6a752b"
|
"sha256:f3efb849d6f7ef4b9788a0eda2e896b975e0ebf1d6bf3dcabea63fd698e5b0b5"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.2.5"
|
"version": "==2.2.6"
|
||||||
},
|
},
|
||||||
"nltk": {
|
"nltk": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1365,12 +1368,12 @@
|
|||||||
},
|
},
|
||||||
"ocrmypdf": {
|
"ocrmypdf": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:362fb80043ef8df9cd2356a9713e4edd47623c90ae3ce430c193c6fd80cb3697",
|
"sha256:24b4d7454bbcbec3b017e55fcc5827c3da24c8f0abbb1efdf695020bbf7ca47f",
|
||||||
"sha256:c5c86223aa7f860734ce5db84975bbca223251357e1a6160291ff64019aee185"
|
"sha256:dd08d32b3d989ba6d7b9f0dcebe3c1bcc049d06609916ac476c1f985b7605111"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.10'",
|
"markers": "python_version >= '3.10'",
|
||||||
"version": "==16.6.1"
|
"version": "==16.6.2"
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1407,105 +1410,105 @@
|
|||||||
},
|
},
|
||||||
"pi-heif": {
|
"pi-heif": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0305ea6e108979eee370fed0486df8b90ff25c619db9d9d539b00603e139626f",
|
"sha256:06e57bebaeadc3d708744bc3d2e4216d86907a2154c7e64ff4198a965e7f1a16",
|
||||||
"sha256:030df62463a03053d68af01fb3fafb3dae3bc578b8da24280f0787469c22bdd0",
|
"sha256:09c58325a8148b62a37c1973dc6aff7c5ccd0421bb711ff2697fccdea3e1a9d5",
|
||||||
"sha256:05b938736a4712fbc1224d4207ceb226670568f7db329b61895876c1ae7fe241",
|
"sha256:0c77bf122504eafc12841cfe03413048ad0af3e5f17b43ac8cfd9930830160aa",
|
||||||
"sha256:086d101461f0580b621de1cf24e6cf9c136015e6ff9408fec7f254c2c4f49243",
|
"sha256:1077487878d2fd7a63fa3c65c96de6213c6c349f74b8c8625164e8edf4617441",
|
||||||
"sha256:13146a8d4873198cf614c70ea344b69932637c28fb506427caffe9b951cc5694",
|
"sha256:1733631b37ee01a4d0b2bf4f727e5a13815ccf5d02b12a1c00edb258899645b5",
|
||||||
"sha256:1371a3a34bffdc98b5bacf48b01c8bba0fd49183fd425a6f152390fda792e8da",
|
"sha256:2295ae1ed8e03fd96e988dba5bf4b179496093cb89c84bc326fcb8cf17c45b28",
|
||||||
"sha256:137af16fc31f8862775a82287e0c1ea0627fb0e9ffa53fccf84cacd1f4e0a90a",
|
"sha256:235ca775c4500c30dc046352bdfddf97f909645cc187e5f382cd2ab4bf630d81",
|
||||||
"sha256:18b8c3269d1e07f85d146217616b117f5f2c8142328c6669a46d8d762699aadb",
|
"sha256:274495e3a8495899ee8a8cfce5d1d3123217aa1093bf096077cdbf78d99ce712",
|
||||||
"sha256:1f482ac86090c0d8adf65bd3a6b97f485ceaa0876123225913e3e2ac687e5bfe",
|
"sha256:2f4f034aed9443ce811f0700dfaa0bbaae55ea331371ff85a18a66432764d353",
|
||||||
"sha256:2484c8b5f447bc0018d9045faea9880e177c818c89eb333239aa02b9ed4a9ffb",
|
"sha256:32648f66b95cec8bada6e3928d6eeed1f6cf9c4b73c7e1924cf84eec1ee8cda8",
|
||||||
"sha256:2a38a50b33cec5261037e625c35270656ec16c2febdcbbb529a9a96857b16a5b",
|
"sha256:35995f4ecf73425d71c68c24f54d7945316667b0b6f2f56928f926d94f198b58",
|
||||||
"sha256:323b7d1ed4a702fe7b8ca2f959f01f33879e38c413542408d522383b65857492",
|
"sha256:35a72089673bf0c9d7d0de994808718a91b2240e493a474c99ebcc3b7d536d96",
|
||||||
"sha256:33ab67a27bcbe24840bb26bf71a097e66c1ac63e1c5c155456913b8f933c9582",
|
"sha256:3b623724c75b3049fd10e268d3283f98be643b0e421264d405aa7c4bc310383a",
|
||||||
"sha256:33cf72ec7d344ae681046455ab7b2d3210e8369ed46f921038fd980ba1b5b27f",
|
"sha256:3ecd96cf9dad1ac9f4126771d164e901e668a8f28293ebccc3178b2c6f2bceb0",
|
||||||
"sha256:393868800055271ae7ad876055e7b3f2eae5ab2fe1980ebe28a5761725c2c189",
|
"sha256:4902cdb84e75505e1d9abdd5aff1e6dcfebe569ec825162d68a4a399a43689a4",
|
||||||
"sha256:397e7839682771a21e7756a4822b4bc45251805d3188bb6275eb51fd63e6c1b2",
|
"sha256:4b6a4fb3e73a7a859ba9ecf6d564e0445e961779145fff2282440915fe55310f",
|
||||||
"sha256:3bc6d86cdc42c22fe0821db2771eefa7e9651922668dd008c1cf8d786c1b13cc",
|
"sha256:504c6912fb60aeb031644f37ac0b7edfdc02bc0023cf1eec2995cdc1486a6c43",
|
||||||
"sha256:3e00bab005d97e6a60b2a52e9fc14fb8d50d9a7ff290d52b0e6491848e853550",
|
"sha256:59effc063d4715daa983ab8492e6d4bb104134a44821677185dfb99e9d16314d",
|
||||||
"sha256:4908b6e6be10062bf06dd5512184b1ad495291b73aa8f0b82f0fc9d66f2bcc1b",
|
"sha256:671a57cb82430aa0c43f51f881888d69a6274236ee6f8e084270549feec7bb56",
|
||||||
"sha256:52436771e1e9198145a3db25885ec9fda4eda11f5753e1317ae9459b190cd6a4",
|
"sha256:6a82187ac503ca57b5cbfce481e3ec9b18752b8dd3c259434d020de6adc9dcc2",
|
||||||
"sha256:5ca9ccfda3b114229c92b5470f8fe451b5d07f4c8677edc3525403ef379b53ba",
|
"sha256:75da9aaf1b4a4c0bf56b0e3f75bbfa116c07b93be3f669b621d3a5b2ae5b4b44",
|
||||||
"sha256:627cc28de13eab9eabef9931c0ae260bdeb61200f6ce3d2cb8444a6979053ef9",
|
"sha256:7c14255ccfdfa7719b664b7a47493412212acd6c075d3af7036adff34be29364",
|
||||||
"sha256:695417ff1050cf79bca5c47bead807d7eb30fcf76fd64457f686a6342712284f",
|
"sha256:8c7808526530d0c534b925b1b9e9477ffb1aefa5aa4a356487f0c839e74933a2",
|
||||||
"sha256:6edf3cf270838d26329e1f0d93a3a85963e4f811de8e80173e18a2f137020deb",
|
"sha256:8c83b87e1ca0950b5046963edfe450e04c6c8cf61ad21647898d563e45c555cb",
|
||||||
"sha256:81e0f769d62f7af250518a99c75c3cc357147ab78ad673b5c0e96b3c1e3ef21b",
|
"sha256:918f863cd86e3550aa1caa3f2f6c176c922a7180025b582d5e20bd2a1e8c64d4",
|
||||||
"sha256:834b188f4963ac29b2ea92153e7d437993d8dd9e5adbff1640695563832f2157",
|
"sha256:988619231bdd89bd39093e8d942397a07b832197d197e3c89d39a8614b051645",
|
||||||
"sha256:849d6ff186128d563b494cd463abc156a73ad4598b38dcaed0d52c0e78ec8cc4",
|
"sha256:9c9558a511f7455230daf7fd36f024cc54c79315abdd272e73df2d655504088c",
|
||||||
"sha256:89c8e89b7615a0aa717f366169c4be31451af72a937314104cc88cbe40e1da11",
|
"sha256:9e64cabc54900210b2e6ca8e7f6f0e496cd4e94e533f6a5bdf658c9204c7bc1a",
|
||||||
"sha256:8b71571e792389151a679f4cb477a54ee1f3834d4b21c2f9e272068a4166964a",
|
"sha256:9ed397da8d10743d8f3499f636c8c15db11311d8d73b8ef120b364896b229f56",
|
||||||
"sha256:9050f675bdc43333c1ac6aab734e253a3ba92a0efc8014d3431953add906ead5",
|
"sha256:a36d29a5ebce2541e11a104fbe2dacac25aa2b9f801fae5e28a13da6557bc694",
|
||||||
"sha256:90e1e4ccd08e7256d1a19519b546efd7d5baf75337d522cbaa813fd6155675ec",
|
"sha256:a48ed9c2cca45d464c09d56f71bfaa85ec3e30c8c8bcb43e5f51b71301b13352",
|
||||||
"sha256:b23d891b4b788a00fb8e0c170e4744d86708bc682b3bf095143b10074bdd694c",
|
"sha256:aa6d929305b62bb391066c2c3ee2ed219bdc5ea564f36091b50f942faf524815",
|
||||||
"sha256:b6bfcc917d3b11f6bcf80db8c64f721ce1ab7335459c0f109e933ca92e6dfb1a",
|
"sha256:b3c111a39a08a56cc6b3c5f381a352d635fbe161d3aa9307a35e14a372bbbb9c",
|
||||||
"sha256:b8cd2a83ee8cc597ddf7e13d2adec60b7206357ff55649c4aea2f4c76bdb11c2",
|
"sha256:b8617e40bba3b654496138ff6a0c99a14f58be012c41b2fdde9c1ba780944f14",
|
||||||
"sha256:d002ca0b10e39b3f034385f1d453f4effd94bf83bbb4464ee72a386dcdbb1c4c",
|
"sha256:bec08ae26a3f73a62844fc7969e6903af7c13dfe3eab34846ffaaff245894c2a",
|
||||||
"sha256:d57f48a08b0cdc492e17fe33312b47356a17fcf6a93c508a9e503e497e03ef4f",
|
"sha256:c37a4e0f4ef417d6bed1854e8176cacb7d9522003a9892ef0872c92909127f8f",
|
||||||
"sha256:d6a4d350ac12fb5f45bc83eb35747156a4faa12d001dcdd9525a3912ae5b4d7a",
|
"sha256:c6a5bca23e86449b8bc7821701013fd4c4cd0b9083caac72eeefaf5e3de022cc",
|
||||||
"sha256:d7866ea2735d8cbc7501a98735a940ea22098303e340f2218caad3427f261abb",
|
"sha256:c9c21dec9b0f0d0bd659687f53f02cbc59b86f1cdff5e14da979e370e185fac8",
|
||||||
"sha256:e81e28f0c7330133b5b1ce7e1ad118663eb9eac4cad6638bfca07c0eb761129a",
|
"sha256:cbb0a2bc1c332664dd3bcbf96dd2f290ecabd1c9088b174412c29fafb667cd54",
|
||||||
"sha256:eadcc3cec45d3c021e4c41c5a85e56f4c2b8b8942fa9a1720d9376b29c2e39cb",
|
"sha256:cd29361e0c156bc5bf0adeb58081a1955b2f02f9caa8bed30afdd595ff9a4745",
|
||||||
"sha256:ee28de31da7d1a8cc08e4fa1b5a1c095b3e1c8be5edd5cf26c8ac4c365b806b3",
|
"sha256:d2b57c7c8b7f126970e2d655ac7b6b480a27a022b619e7463c071963571e498b",
|
||||||
"sha256:f082f12fb3954ac0140e9eead2d3a2be4aa60aa834a3998665175fde67f50793",
|
"sha256:e0e8c432b43cb982f8816218e6997a4e27eec414da42eade5b7585a68776dea7",
|
||||||
"sha256:f3f1e59b802d96c0dcf1b91ed2cc7d196a94b9e3d985ef80a86700e16e45bb28",
|
"sha256:e471c29555bf8c9567d4176eabe85aedc27ec1e0d3af1616fdf8a5b4c45b1757",
|
||||||
"sha256:f4b94f5ea91cfe180ead680528a197da8f77ff27b339aceb169bd7e2a51b53b1",
|
"sha256:e47cda8937cb2cdac2b9071e6380ec0c15bd6fd5f871d3d69bc25f9a523d4916",
|
||||||
"sha256:f63b5ab190697c91dec482112bfa90751e48070899f4888317e943075dccf0be",
|
"sha256:f51a518f659ff79db74b9044a52bf5a45ed5f0d7f2eca5afdfe55ae1bafcd7a8",
|
||||||
"sha256:ff50c2c090c2af6347e9caa8f2e3998c29f9ff8d4fac625bd48e0072f2133464"
|
"sha256:f91a088b2b20d988c98aef6c85ca84ebf4829a4b65d800e72775b4759fa3310c"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.9'",
|
"markers": "python_version >= '3.9'",
|
||||||
"version": "==0.20.0"
|
"version": "==0.21.0"
|
||||||
},
|
},
|
||||||
"pikepdf": {
|
"pikepdf": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:14cc769aafecedb25a112da54cbf3176533cd87dcf05f45308a0a28cfca55ce7",
|
"sha256:008f207d7efcf8f5369e79f23638e50540a17c616517747738090b7cfddad8a6",
|
||||||
"sha256:17ab91fca8a8a1e5dd52483d647352ec8875f4bb9eef8b3fa186378c5c7815b1",
|
"sha256:009e2aa4074faa60b5f107615616132e4635dfd7579dabf21f2862de0f84e216",
|
||||||
"sha256:1c3472e5570fef3a7714a41b09a81e3dcbbce3f2a0d6c43c4f197b40288a4aed",
|
"sha256:0108c063bc56dc2dbfc87f20533a728342a938f4c85e39773866b71255aa8388",
|
||||||
"sha256:1d0b983ef7481502ce0439867280b00846ba2eecd37fa5906a4ff3e445895b85",
|
"sha256:0240a27efa519fa534a543394f17d3a51d6468be43f218fe810506a106f131b1",
|
||||||
"sha256:1f1f53368c8a25f8c5e96dc19e57c0ad3d8bd7f259ca730ac5b0bd9d21747d5a",
|
"sha256:0942201f49a08d877f9759cd83e7bcd12c6a4355b85f41086b2791ba75671382",
|
||||||
"sha256:1f6a9da26ae2472bdaaa038720778a1ac15af908c2d4b3670b90318ab00eaed1",
|
"sha256:0bdac215f90b16ce05e854c71eba34021dd4226e054a02ff555dd95f5424f40d",
|
||||||
"sha256:20b7e5c5f93b674214a8ebff79c9793cab07261930e35d60110404fa7cdbcb56",
|
"sha256:0c365bafc5c0fcf88fee07818fe2234069a193349ec79efb127b9037b7cfc8af",
|
||||||
"sha256:24e90a325f6e9a3eb0d043deddf1dbb5f5aba20c5ca31430f49397e9c5888b1e",
|
"sha256:0d49b16c3e09de600c5baa7f903ab68e31e1210ceb523185552b173e68371141",
|
||||||
"sha256:30467da3000850a83da3e17c37e1fd6cc41005d6232f162725da15736996a39f",
|
"sha256:0ee528d252b34e8760f8f8ec1517b35fb4bc4457c8cab6bcd7f0c128f28609ba",
|
||||||
"sha256:343328acc542aee0d5284fb0cb904cec6bfbedb860d3fa24dfeab306b05847cc",
|
"sha256:1583c7b43a6c8986ec6e9b2833f1b01877338727a73630e533aad739ddc14501",
|
||||||
"sha256:3cdfc4f054efd75eaad3582e4ef94ff4ebc93c58de21f2769f071af984500a9a",
|
"sha256:17817e739a0e9507b5f85463e90f376f54a1832667c6c315ad9fb0086bb1edf7",
|
||||||
"sha256:3d11f89276e7ae48c763b75fd3322cf6ebf0d45580778ad9423e56dca799dd9d",
|
"sha256:277eef7c6ea5ca3b9f8a00005c4ad19ff3ad166800c96e1a9f866bd2ae853f4b",
|
||||||
"sha256:3d5e2bf5e376968ddbe1f5e409174d1d0fde04ddbcda6509b1ad63664f4a46aa",
|
"sha256:2935d152612cc0af7ed619d39ad56d34e6fe56d11efa36d6eae0a7e3674ca6aa",
|
||||||
"sha256:3d85ecf8761f7fa8f4270176b7f2ebe92909d652867d684526e3d67a34497c41",
|
"sha256:2b0a1e066e57d01ccef2fb46fc046731c9bd60d268a99b808fbd1807eb81fb84",
|
||||||
"sha256:45856be7fd9793090b258990896540e77d19908f26666286476c601ac65813d6",
|
"sha256:3be7016a36b7099338f4290f9f3b5de7dadf4a446ad49d89f96b95bd89167d94",
|
||||||
"sha256:564e0f1a2299f4a8a5ae6ed3b29b683dac331bc3c1a5a701e579a1e6517ced77",
|
"sha256:473589dd7ccd0e4b11f82a7fb366984d85fa1fc998ab0f8695f1eb6eb1cdce85",
|
||||||
"sha256:56bbbc4a8811fa440be22c7434e2e00267e363fc95a2f77aee62dcb6561ce119",
|
"sha256:497f45bbe50f0031920af28da5e950cf577894236bd1f3feda1bad3858a9dd38",
|
||||||
"sha256:5cd676aaa5dfd2dde9dcc24950e23ea1a834ece3419cc54aaf91cfa0bb604632",
|
"sha256:50db5731f3ec853980863e096789bbe1027425fa90ce36fe9af34dec3e8b98fe",
|
||||||
"sha256:60d540f8b7eef97410b29adb10ef1afd52e4107a49c019bf580c54d03e2f39d4",
|
"sha256:5dadf9c8db722fbcd694445e8e6f6501475cf66773783aeaacd054c128898356",
|
||||||
"sha256:61bd67cf5f10f3fa20a1586f4210427d3878030062d861b37e20d662d214ec22",
|
"sha256:701390e3a718a9588599cafc34ca2cb9a5a96109209088b46f44b787430e547b",
|
||||||
"sha256:64ed1993998bcb4da10663c98398c92c135b951696baba3f9e7d93b86b1c6ac8",
|
"sha256:7bc3f951c8646f7bcd55f72b678801f3e0de17f7fb9329b4550ee64f9a566343",
|
||||||
"sha256:6b02fec9fbacf2bea1fc25a8a5dcdce27a6ee598c3a67166b371a792b42e45a8",
|
"sha256:8390abb7beb80e53c92b587efe7609476a017dcd67430bd7f5c4698108f21b97",
|
||||||
"sha256:6c458976fb0540ea9ec79b0ebc6641df982e5b0ff93301d6935d70c7b051dbd1",
|
"sha256:8a2204695f270fed41cc872c215a0dd9deac510845f9879aaed001303ecd3d88",
|
||||||
"sha256:703d72dac7ebc0d342afba57f5b91ad5043f67dd351f4d95a7c8e5d360759e69",
|
"sha256:90cbb667fec4693834eeb9b5e58e62b5ccb53face183a54eaf5a157edbc6a9b8",
|
||||||
"sha256:84bb53011e01e976af699da897df290681c45e37a63330e3b05ec8eb08d3a9a9",
|
"sha256:9200ce41c3c22bbd2c292666f7c27f8274f79ea1f99f929ff8cfde8ee3560c96",
|
||||||
"sha256:868aa504e07804dac3dc7f8a20befc6eaa2576baf7f3a7013826f10de6da74ce",
|
"sha256:9470c1782143a62005d0b3dac59bbe394f65d9122c8f5966afa63b5c44aa51b3",
|
||||||
"sha256:8b4903e409a1f57f8c24bd6821cdaf26031ae0262feb6214662d9d1ecad27386",
|
"sha256:97f71f87601c9931bea157d268acd1c1f5a78cf48bad0916403fbbc3ccce78cb",
|
||||||
"sha256:9fa695ede603884612a62494820669662af0d42fc37b6030a65990d20fe62cc8",
|
"sha256:9a9bca70ba4bad72082f443f120064a1fde95d288e59c880d60d78386e99df2d",
|
||||||
"sha256:9fa773e3d9c5c6cc8f65b7043e56556e7e9534f5d0851c645a2942511c385081",
|
"sha256:9b683f32681b62be7c772bcd84168c0fe616562aac99ca9258ce1b16fe5c3580",
|
||||||
"sha256:a6383bbc6feb14b42f0e9e7a85586e4ea566831786decc7b39863290687044de",
|
"sha256:9d0337a8c68a0939a448b55e78cc267b2eebc8a711bbd9471bf497e2f1265828",
|
||||||
"sha256:bc5e435f4ffd640afc2acdd01f84e106623868558656bf038c2f0627e27b9b0a",
|
"sha256:a2826183f5d4811ace4049fc23163a6b8540dca8bbe5083f5ac7020a629531a0",
|
||||||
"sha256:bc637626551f9ca98140d4fa84964ff07aa89faa3af91aca990041c11db10a9d",
|
"sha256:af863cbb8aaafd2f9a15e41ade19d0a85d85d51b2d167f29a2b11985c8def154",
|
||||||
"sha256:c7bf91e24b93c8cf5e9441e86b04edc21779e64d8d48a9311a7fc678d396787f",
|
"sha256:b42c9c8d9459e22ebeefc727f6a817ff0c5f23fbcd1bdb33cc12d00c943fb801",
|
||||||
"sha256:cfa901b74045623c7e3929c3ca0d5ceab01bdbc48888375cf98217509147bcd6",
|
"sha256:b4a699cc88e4b19df68f1d62765368e274488949abf7c08709757207254e4c5b",
|
||||||
"sha256:d366f75d9bcefb666e208c8aa1e602de93f87f89515c9aaea65167d8261ef3fe",
|
"sha256:b6513a119904cddd00c54cf8d65ed5183e416b06096bb90145c250b560075755",
|
||||||
"sha256:da0cb1272571916b025c3954a2f4f690784d3da41a485ffd8156fa3cd8b88019",
|
"sha256:cd83787d151b58c127cadee01bd51e019cc772d337541ef91937c24d130392bc",
|
||||||
"sha256:db04a42b8d5b3d5422b17c0422e03b1bb9349910f8ce5d336df200e2f9ce6ecd",
|
"sha256:d1bf723a35e6f54a0e662c3f65f5d133320fb23346e50db6feb528c71c688d6c",
|
||||||
"sha256:dd2de2a28da411713d169a1dcab805239f2fe3d5ab3e070d5e309977f92596df",
|
"sha256:dc6eea6c6137d2a1d5866158fe54f66baa04d3fa954b3d880a8e4b1a1776426c",
|
||||||
"sha256:ddeaae67bb59d60538f3fc64459b83ff12eea34c399780527cf8e6d867254c2c",
|
"sha256:dda896ca38d065e2aefe6160a776211c84c44c33677126b05eb75dbd369bb8cd",
|
||||||
"sha256:e42da36b2f449200a9b4dfbb5d4c661f5b118dd763799c077e9edb1b86df8cd1",
|
"sha256:deccfb769dae0d0746d2bc373fe0bfa1b2c5b06c473dd126be37f80cfd7ec99d",
|
||||||
"sha256:f8ab6172825b95fa313162d199c073ea9515fbe72d8e8cfd824586c8537e3c08",
|
"sha256:e893aa12673d1e3a3daf3cc76b652104dee27006d745d83dbd7a12183ca83bfd",
|
||||||
"sha256:f9360958e96f7979355b8f5af5409332f284fdcbf3d4985649635f5592ee05fc",
|
"sha256:e9e004efab21fe220bf2d2c05234e1a743349aec7fa1b4069ed6eb6b05898856",
|
||||||
"sha256:f961e15db95d6fc2bf4d9c8fe3e7a843366ba956c6d3fcdf98613f0eb0a1bc46",
|
"sha256:edddada6df766f571146ea0f902124c8c2953b79c2108918245f344466df0db1",
|
||||||
"sha256:fb95b6e251e90788391326ba27bbf021ae2ca3613573bcc887f8e6f65e2a433f"
|
"sha256:fe705959bc7188c355f8fdbaf93dcac90cc5fe1c5762c55e59c27f6ce23c398c"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.9'",
|
"markers": "python_version >= '3.9'",
|
||||||
"version": "==9.4.0"
|
"version": "==9.4.2"
|
||||||
},
|
},
|
||||||
"pillow": {
|
"pillow": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -2095,98 +2098,95 @@
|
|||||||
},
|
},
|
||||||
"setproctitle": {
|
"setproctitle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:00e6e7adff74796ef12753ff399491b8827f84f6c77659d71bd0b35870a17d8f",
|
"sha256:020ea47a79b2bbd7bd7b94b85ca956ba7cb026e82f41b20d2e1dac4008cead25",
|
||||||
"sha256:059f4ce86f8cc92e5860abfc43a1dceb21137b26a02373618d88f6b4b86ba9b2",
|
"sha256:02ca3802902d91a89957f79da3ec44b25b5804c88026362cb85eea7c1fbdefd1",
|
||||||
"sha256:088b9efc62d5aa5d6edf6cba1cf0c81f4488b5ce1c0342a8b67ae39d64001120",
|
"sha256:0361428e6378911a378841509c56ba472d991cbed1a7e3078ec0cacc103da44a",
|
||||||
"sha256:0d3a953c50776751e80fe755a380a64cb14d61e8762bd43041ab3f8cc436092f",
|
"sha256:04d6ba8b816dbb0bfd62000b0c3e583160893e6e8c4233e1dca1a9ae4d95d924",
|
||||||
"sha256:1342f4fdb37f89d3e3c1c0a59d6ddbedbde838fff5c51178a7982993d238fe4f",
|
"sha256:06c16b7a91cdc5d700271899e4383384a61aae83a3d53d0e2e5a266376083342",
|
||||||
"sha256:184239903bbc6b813b1a8fc86394dc6ca7d20e2ebe6f69f716bec301e4b0199d",
|
"sha256:0855006261635e8669646c7c304b494b6df0a194d2626683520103153ad63cc9",
|
||||||
"sha256:195c961f54a09eb2acabbfc90c413955cf16c6e2f8caa2adbf2237d1019c7dd8",
|
"sha256:091f682809a4d12291cf0205517619d2e7014986b7b00ebecfde3d76f8ae5a8f",
|
||||||
"sha256:1f5d9027eeda64d353cf21a3ceb74bb1760bd534526c9214e19f052424b37e42",
|
"sha256:0ad212ae2b03951367a69584af034579b34e1e4199a75d377ef9f8e08ee299b1",
|
||||||
"sha256:200620c3b15388d7f3f97e0ae26599c0c378fdf07ae9ac5a13616e933cbd2086",
|
"sha256:0b19813c852566fa031902124336fa1f080c51e262fc90266a8c3d65ca47b74c",
|
||||||
"sha256:200ede6fd11233085ba9b764eb055a2a191fb4ffb950c68675ac53c874c22e20",
|
"sha256:0b6a4cbabf024cb263a45bdef425760f14470247ff223f0ec51699ca9046c0fe",
|
||||||
"sha256:21112fcd2195d48f25760f0eafa7a76510871bbb3b750219310cf88b04456ae3",
|
"sha256:0baadeb27f9e97e65922b4151f818b19c311d30b9efdb62af0e53b3db4006ce2",
|
||||||
"sha256:224602f0939e6fb9d5dd881be1229d485f3257b540f8a900d4271a2c2aa4e5f4",
|
"sha256:0f6661a69c68349172ba7b4d5dd65fec2b0917abc99002425ad78c3e58cf7595",
|
||||||
"sha256:287490eb90e7a0ddd22e74c89a92cc922389daa95babc833c08cf80c84c4df0a",
|
"sha256:10a78fce9018cc3e9a772b6537bbe3fe92380acf656c9f86db2f45e685af376e",
|
||||||
"sha256:2982efe7640c4835f7355fdb4da313ad37fb3b40f5c69069912f8048f77b28c8",
|
"sha256:122c2e05697fa91f5d23f00bbe98a9da1bd457b32529192e934095fadb0853f1",
|
||||||
"sha256:2df2b67e4b1d7498632e18c56722851ba4db5d6a0c91aaf0fd395111e51cdcf4",
|
"sha256:149fdfb8a26a555780c4ce53c92e6d3c990ef7b30f90a675eca02e83c6d5f76d",
|
||||||
"sha256:2e4a8104db15d3462e29d9946f26bed817a5b1d7a47eabca2d9dc2b995991503",
|
"sha256:1a2041b5788ce52f218b5be94af458e04470f997ab46fdebd57cf0b8374cc20e",
|
||||||
"sha256:2e71f6365744bf53714e8bd2522b3c9c1d83f52ffa6324bd7cbb4da707312cd8",
|
"sha256:1a88e466fcaee659679c1d64dcb2eddbcb4bfadffeb68ba834d9c173a25b6184",
|
||||||
"sha256:334f7ed39895d692f753a443102dd5fed180c571eb6a48b2a5b7f5b3564908c8",
|
"sha256:1bba0a866f5895d5b769d8c36b161271c7fd407e5065862ab80ff91c29fbe554",
|
||||||
"sha256:33c5609ad51cd99d388e55651b19148ea99727516132fb44680e1f28dd0d1de9",
|
"sha256:1d2a154b79d5fb42d1eff06e05e22f0e8091261d877dd47b37d31352b74ecc37",
|
||||||
"sha256:37a62cbe16d4c6294e84670b59cf7adcc73faafe6af07f8cb9adaf1f0e775b19",
|
"sha256:1eb115d53dc2a1299ae72f1119c96a556db36073bacb6da40c47ece5db0d9587",
|
||||||
"sha256:38ae9a02766dad331deb06855fb7a6ca15daea333b3967e214de12cfae8f0ef5",
|
"sha256:202eae632815571297833876a0f407d0d9c7ad9d843b38adbe687fe68c5192ee",
|
||||||
"sha256:38da436a0aaace9add67b999eb6abe4b84397edf4a78ec28f264e5b4c9d53cd5",
|
"sha256:24f3c8be826a7d44181eac2269b15b748b76d98cd9a539d4c69f09321dcb5c12",
|
||||||
"sha256:415bfcfd01d1fbf5cbd75004599ef167a533395955305f42220a585f64036081",
|
"sha256:28b8614de08679ae95bc4e8d6daaef6b61afdf027fa0d23bf13d619000286b3c",
|
||||||
"sha256:417de6b2e214e837827067048f61841f5d7fc27926f2e43954567094051aff18",
|
"sha256:2b0080819859e80a7776ac47cf6accb4b7ad313baf55fabac89c000480dcd103",
|
||||||
"sha256:477d3da48e216d7fc04bddab67b0dcde633e19f484a146fd2a34bb0e9dbb4a1e",
|
"sha256:2b2ef636a6a25fe7f3d5a064bea0116b74a4c8c7df9646b17dc7386c439a26cf",
|
||||||
"sha256:4a6ba2494a6449b1f477bd3e67935c2b7b0274f2f6dcd0f7c6aceae10c6c6ba3",
|
"sha256:2c3b1ce68746557aa6e6f4547e76883925cdc7f8d7c7a9f518acd203f1265ca5",
|
||||||
"sha256:4fe1c49486109f72d502f8be569972e27f385fe632bd8895f4730df3c87d5ac8",
|
"sha256:3058a1bb0c767b3a6ccbb38b27ef870af819923eb732e21e44a3f300370fe159",
|
||||||
"sha256:507e8dc2891021350eaea40a44ddd887c9f006e6b599af8d64a505c0f718f170",
|
"sha256:30bb223e6c3f95ad9e9bb2a113292759e947d1cfd60dbd4adb55851c370006b2",
|
||||||
"sha256:53bc0d2358507596c22b02db079618451f3bd720755d88e3cccd840bafb4c41c",
|
"sha256:317218c9d8b17a010ab2d2f0851e8ef584077a38b1ba2b7c55c9e44e79a61e73",
|
||||||
"sha256:554eae5a5b28f02705b83a230e9d163d645c9a08914c0ad921df363a07cf39b1",
|
"sha256:342570716e2647a51ea859b8a9126da9dc1a96a0153c9c0a3514effd60ab57ad",
|
||||||
"sha256:59335d000c6250c35989394661eb6287187854e94ac79ea22315469ee4f4c244",
|
"sha256:3b40d32a3e1f04e94231ed6dfee0da9e43b4f9c6b5450d53e6dd7754c34e0c50",
|
||||||
"sha256:5a740f05d0968a5a17da3d676ce6afefebeeeb5ce137510901bf6306ba8ee002",
|
"sha256:3e55d7ecc68bdc80de5a553691a3ed260395d5362c19a266cf83cbb4e046551f",
|
||||||
"sha256:5bc94cf128676e8fac6503b37763adb378e2b6be1249d207630f83fc325d9b11",
|
"sha256:475986ddf6df65d619acd52188336a20f616589403f5a5ceb3fc70cdc137037a",
|
||||||
"sha256:64286f8a995f2cd934082b398fc63fca7d5ffe31f0e27e75b3ca6b4efda4e353",
|
"sha256:47669fc8ed8b27baa2d698104732234b5389f6a59c37c046f6bcbf9150f7a94e",
|
||||||
"sha256:664698ae0013f986118064b6676d7dcd28fefd0d7d5a5ae9497cbc10cba48fa5",
|
"sha256:4afcb38e22122465013f4621b7e9ff8d42a7a48ae0ffeb94133a806cb91b4aad",
|
||||||
"sha256:68f960bc22d8d8e4ac886d1e2e21ccbd283adcf3c43136161c1ba0fa509088e0",
|
"sha256:4ee5b19a2d794463bcc19153dfceede7beec784b4cf7967dec0bc0fc212ab3a3",
|
||||||
"sha256:69d565d20efe527bd8a9b92e7f299ae5e73b6c0470f3719bd66f3cd821e0d5bd",
|
"sha256:5519f2a7b8c535b0f1f77b30441476571373add72008230c81211ee17b423b57",
|
||||||
"sha256:6a143b31d758296dc2f440175f6c8e0b5301ced3b0f477b84ca43cdcf7f2f476",
|
"sha256:59e0dda9ad245921af0328035a961767026e1fa94bb65957ab0db0a0491325d6",
|
||||||
"sha256:6a249415f5bb88b5e9e8c4db47f609e0bf0e20a75e8d744ea787f3092ba1f2d0",
|
"sha256:5a97d37ee4fe0d1c6e87d2a97229c27a88787a8f4ebfbdeee95f91b818e52efe",
|
||||||
"sha256:6b9e62ddb3db4b5205c0321dd69a406d8af9ee1693529d144e86bd43bcb4b6c0",
|
"sha256:5d758e2eed2643afac5f2881542fbb5aa97640b54be20d0a5ed0691d02f0867d",
|
||||||
"sha256:7f1d36a1e15a46e8ede4e953abb104fdbc0845a266ec0e99cc0492a4364f8c44",
|
"sha256:5edd01909348f3b0b2da329836d6b5419cd4869fec2e118e8ff3275b38af6267",
|
||||||
"sha256:816330675e3504ae4d9a2185c46b573105d2310c20b19ea2b4596a9460a4f674",
|
"sha256:5f0521ed3bb9f02e9486573ea95e2062cd6bf036fa44e640bd54a06f22d85f35",
|
||||||
"sha256:87e668f9561fd3a457ba189edfc9e37709261287b52293c115ae3487a24b92f6",
|
"sha256:62d66e0423e3bd520b4c897063506b309843a8d07343fbfad04197e91a4edd28",
|
||||||
"sha256:897a73208da48db41e687225f355ce993167079eda1260ba5e13c4e53be7f754",
|
"sha256:66821fada6426998762a3650a37fba77e814a249a95b1183011070744aff47f6",
|
||||||
"sha256:8c331e91a14ba4076f88c29c777ad6b58639530ed5b24b5564b5ed2fd7a95452",
|
"sha256:6b17655a5f245b416e127e02087ea6347a48821cc4626bc0fd57101bfcd88afc",
|
||||||
"sha256:950f6476d56ff7817a8fed4ab207727fc5260af83481b2a4b125f32844df513a",
|
"sha256:6dc3d656702791565994e64035a208be56b065675a5bc87b644c657d6d9e2232",
|
||||||
"sha256:9617b676b95adb412bb69645d5b077d664b6882bb0d37bfdafbbb1b999568d85",
|
"sha256:6e61dd7d05da11fc69bb86d51f1e0ee08f74dccf3ecf884c94de41135ffdc75d",
|
||||||
"sha256:9e3b99b338598de0bd6b2643bf8c343cf5ff70db3627af3ca427a5e1a1a90dd9",
|
"sha256:726aee40357d4bdb70115442cb85ccc8e8bc554fc0bbbaa3a57cbe81df42287d",
|
||||||
"sha256:a1fcac43918b836ace25f69b1dca8c9395253ad8152b625064415b1d2f9be4fb",
|
"sha256:743836d484151334ebba1490d6907ca9e718fe815dcd5756f2a01bc3067d099c",
|
||||||
"sha256:a680d62c399fa4b44899094027ec9a1bdaf6f31c650e44183b50d4c4d0ccc085",
|
"sha256:754bac5e470adac7f7ec2239c485cd0b75f8197ca8a5b86ffb20eb3a3676cc42",
|
||||||
"sha256:a6d50252377db62d6a0bb82cc898089916457f2db2041e1d03ce7fadd4a07381",
|
"sha256:779006f9e1aade9522a40e8d9635115ab15dd82b7af8e655967162e9c01e2573",
|
||||||
"sha256:a83ca086fbb017f0d87f240a8f9bbcf0809f3b754ee01cec928fff926542c450",
|
"sha256:8ab9f5b7f2bbc1754bc6292d9a7312071058e5a891b0391e6d13b226133f36aa",
|
||||||
"sha256:a911b26264dbe9e8066c7531c0591cfab27b464459c74385b276fe487ca91c12",
|
"sha256:8c52b12b10e4057fc302bd09cb3e3f28bb382c30c044eb3396e805179a8260e4",
|
||||||
"sha256:ab2900d111e93aff5df9fddc64cf51ca4ef2c9f98702ce26524f1acc5a786ae7",
|
"sha256:90ea8d302a5d30b948451d146e94674a3c5b020cc0ced9a1c28f8ddb0f203a5d",
|
||||||
"sha256:ab92e51cd4a218208efee4c6d37db7368fdf182f6e7ff148fb295ecddf264287",
|
"sha256:939d364a187b2adfbf6ae488664277e717d56c7951a4ddeb4f23b281bc50bfe5",
|
||||||
"sha256:accb66d7b3ccb00d5cd11d8c6e07055a4568a24c95cf86109894dcc0c134cc89",
|
"sha256:97f1f861998e326e640708488c442519ad69046374b2c3fe9bcc9869b387f23c",
|
||||||
"sha256:ad6d20f9541f5f6ac63df553b6d7a04f313947f550eab6a61aa758b45f0d5657",
|
"sha256:9c76e43cb351ba8887371240b599925cdf3ecececc5dfb7125c71678e7722c55",
|
||||||
"sha256:aeaa71fb9568ebe9b911ddb490c644fbd2006e8c940f21cb9a1e9425bd709574",
|
"sha256:9c9d7d1267dee8c6627963d9376efa068858cfc8f573c083b1b6a2d297a8710f",
|
||||||
"sha256:af2c67ae4c795d1674a8d3ac1988676fa306bcfa1e23fddb5e0bd5f5635309ca",
|
"sha256:9f9732e59863eaeedd3feef94b2b216cb86d40dda4fad2d0f0aaec3b31592716",
|
||||||
"sha256:af4061f67fd7ec01624c5e3c21f6b7af2ef0e6bab7fbb43f209e6506c9ce0092",
|
"sha256:a166251b8fbc6f2755e2ce9d3c11e9edb0c0c7d2ed723658ff0161fbce26ac1c",
|
||||||
"sha256:b1067647ac7aba0b44b591936118a22847bda3c507b0a42d74272256a7a798e9",
|
"sha256:a46ef3ecf61e4840fbc1145fdd38acf158d0da7543eda7b773ed2b30f75c2830",
|
||||||
"sha256:b5901a31012a40ec913265b64e48c2a4059278d9f4e6be628441482dd13fb8b5",
|
"sha256:a65a147f545f3fac86f11acb2d0b316d3e78139a9372317b7eb50561b2817ba0",
|
||||||
"sha256:bbbd6c7de0771c84b4aa30e70b409565eb1fc13627a723ca6be774ed6b9d9fa3",
|
"sha256:abda20aff8d1751e48d7967fa8945fef38536b82366c49be39b83678d4be3893",
|
||||||
"sha256:bdfd7254745bb737ca1384dee57e6523651892f0ea2a7344490e9caefcc35e64",
|
"sha256:acf41cf91bbc5a36d1fa4455a818bb02bf2a4ccfed2f892ba166ba2fcbb0ec8a",
|
||||||
"sha256:c05ac48ef16ee013b8a326c63e4610e2430dbec037ec5c5b58fcced550382b74",
|
"sha256:adcd6ba863a315702184d92d3d3bbff290514f24a14695d310f02ae5e28bd1f7",
|
||||||
"sha256:c1c84beab776b0becaa368254801e57692ed749d935469ac10e2b9b825dbdd8e",
|
"sha256:b3afa5a0ed08a477ded239c05db14c19af585975194a00adf594d48533b23701",
|
||||||
"sha256:c32c41ace41f344d317399efff4cffb133e709cec2ef09c99e7a13e9f3b9483c",
|
"sha256:b669aaac70bd9f03c070270b953f78d9ee56c4af6f0ff9f9cd3e6d1878c10b40",
|
||||||
"sha256:c3ba57029c9c50ecaf0c92bb127224cc2ea9fda057b5d99d3f348c9ec2855ad3",
|
"sha256:bdaaa81a6e95a0a19fba0285f10577377f3503ae4e9988b403feba79da3e2f80",
|
||||||
"sha256:c7951820b77abe03d88b114b998867c0f99da03859e5ab2623d94690848d3e45",
|
"sha256:cb5fefb53b9d9f334a5d9ec518a36b92a10b936011ac8a6b6dffd60135f16459",
|
||||||
"sha256:c913e151e7ea01567837ff037a23ca8740192880198b7fbb90b16d181607caae",
|
"sha256:cb8a6a19be0cbf6da6fcbf3698b76c8af03fe83e4bd77c96c3922be3b88bf7da",
|
||||||
"sha256:c9a402881ec269d0cc9c354b149fc29f9ec1a1939a777f1c858cdb09c7a261df",
|
"sha256:ceb3ce3262b0e8e088e4117175591b7a82b3bdc5e52e33b1e74778b5fb53fd38",
|
||||||
"sha256:cbf16381c7bf7f963b58fb4daaa65684e10966ee14d26f5cc90f07049bfd8c1e",
|
"sha256:d06990dcfcd41bb3543c18dd25c8476fbfe1f236757f42fef560f6aa03ac8dfc",
|
||||||
"sha256:d4460795a8a7a391e3567b902ec5bdf6c60a47d791c3b1d27080fc203d11c9dc",
|
"sha256:d6e3b177e634aa6bbbfbf66d097b6d1cdb80fc60e912c7d8bace2e45699c07dd",
|
||||||
"sha256:d7f27e0268af2d7503386e0e6be87fb9b6657afd96f5726b733837121146750d",
|
"sha256:db78b645dc63c0ccffca367a498f3b13492fb106a2243a1e998303ba79c996e2",
|
||||||
"sha256:d876d355c53d975c2ef9c4f2487c8f83dad6aeaaee1b6571453cb0ee992f55f6",
|
"sha256:ded03546938a987f463c68ab98d683af87a83db7ac8093bbc179e77680be5ba2",
|
||||||
"sha256:da0d57edd4c95bf221b2ebbaa061e65b1788f1544977288bdf95831b6e44e44d",
|
"sha256:e152f4ab9ea1632b5fecdd87cee354f2b2eb6e2dfc3aceb0eb36a01c1e12f94c",
|
||||||
"sha256:ddedd300cd690a3b06e7eac90ed4452348b1348635777ce23d460d913b5b63c3",
|
"sha256:ef133a1a2ee378d549048a12d56f4ef0e2b9113b0b25b6b77821e9af94d50634",
|
||||||
"sha256:df3f4274b80709d8bcab2f9a862973d453b308b97a0b423a501bcd93582852e3",
|
"sha256:f0f749f07002c2d6fecf37cedc43207a88e6c651926a470a5f229070cf791879",
|
||||||
"sha256:e18b7bd0898398cc97ce2dfc83bb192a13a087ef6b2d5a8a36460311cb09e775",
|
"sha256:f7bc7088c15150745baf66db62a4ced4507d44419eb66207b609f91b64a682af",
|
||||||
"sha256:e5119a211c2e98ff18b9908ba62a3bd0e3fabb02a29277a7232a6fb4b2560aa0",
|
"sha256:f859c88193ed466bee4eb9d45fbc29d2253e6aa3ccd9119c9a1d8d95f409a60d",
|
||||||
"sha256:e5e08e232b78ba3ac6bc0d23ce9e2bee8fad2be391b7e2da834fc9a45129eb87",
|
"sha256:f963b6ed8ba33eda374a98d979e8a0eaf21f891b6e334701693a2c9510613c4c",
|
||||||
"sha256:eae8988e78192fd1a3245a6f4f382390b61bce6cfcc93f3809726e4c885fa68d",
|
"sha256:fa5057a86df920faab8ee83960b724bace01a3231eb8e3f2c93d78283504d598",
|
||||||
"sha256:f05e66746bf9fe6a3397ec246fe481096664a9c97eb3fea6004735a4daf867fd",
|
"sha256:fb693000b65842c85356b667d057ae0d0bac6519feca7e1c437cc2cfeb0afc59",
|
||||||
"sha256:f1da82c3e11284da4fcbf54957dafbf0655d2389cd3d54e4eaba636faf6d117a",
|
"sha256:fc9d79b1bf833af63b7c720a6604eb16453ac1ad4e718eb8b59d1f97d986b98c",
|
||||||
"sha256:f38d48abc121263f3b62943f84cbaede05749047e428409c2c199664feb6abc7",
|
"sha256:ffcb09d5c0ffa043254ec9a734a73f3791fec8bf6333592f906bb2e91ed2af1a"
|
||||||
"sha256:f5e7266498cd31a4572378c61920af9f6b4676a73c299fce8ba93afd694f8ae7",
|
|
||||||
"sha256:fc74e84fdfa96821580fb5e9c0b0777c1c4779434ce16d3d62a9c4d8c710df39",
|
|
||||||
"sha256:ff814dea1e5c492a4980e3e7d094286077054e7ea116cbeda138819db194b2cd"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.3.3"
|
"version": "==1.3.4"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -2206,11 +2206,11 @@
|
|||||||
},
|
},
|
||||||
"sqlparse": {
|
"sqlparse": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4",
|
"sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f",
|
||||||
"sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"
|
"sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==0.5.1"
|
"version": "==0.5.2"
|
||||||
},
|
},
|
||||||
"threadpoolctl": {
|
"threadpoolctl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -2249,12 +2249,12 @@
|
|||||||
},
|
},
|
||||||
"tqdm": {
|
"tqdm": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be",
|
"sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2",
|
||||||
"sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a"
|
"sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==4.67.0"
|
"version": "==4.67.1"
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -2585,79 +2585,74 @@
|
|||||||
},
|
},
|
||||||
"wrapt": {
|
"wrapt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc",
|
"sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d",
|
||||||
"sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81",
|
"sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301",
|
||||||
"sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09",
|
"sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635",
|
||||||
"sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e",
|
"sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a",
|
||||||
"sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca",
|
"sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed",
|
||||||
"sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0",
|
"sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721",
|
||||||
"sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb",
|
"sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801",
|
||||||
"sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487",
|
"sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b",
|
||||||
"sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40",
|
"sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1",
|
||||||
"sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c",
|
"sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88",
|
||||||
"sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060",
|
"sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8",
|
||||||
"sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202",
|
"sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0",
|
||||||
"sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41",
|
"sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f",
|
||||||
"sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9",
|
"sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578",
|
||||||
"sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b",
|
"sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7",
|
||||||
"sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664",
|
"sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045",
|
||||||
"sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d",
|
"sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada",
|
||||||
"sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362",
|
"sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d",
|
||||||
"sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00",
|
"sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b",
|
||||||
"sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc",
|
"sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a",
|
||||||
"sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1",
|
"sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977",
|
||||||
"sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267",
|
"sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea",
|
||||||
"sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956",
|
"sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346",
|
||||||
"sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966",
|
"sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13",
|
||||||
"sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1",
|
"sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22",
|
||||||
"sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228",
|
"sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339",
|
||||||
"sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72",
|
"sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9",
|
||||||
"sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d",
|
"sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181",
|
||||||
"sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292",
|
"sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c",
|
||||||
"sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0",
|
"sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90",
|
||||||
"sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0",
|
"sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a",
|
||||||
"sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36",
|
"sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489",
|
||||||
"sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c",
|
"sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f",
|
||||||
"sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5",
|
"sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504",
|
||||||
"sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f",
|
"sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea",
|
||||||
"sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73",
|
"sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569",
|
||||||
"sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b",
|
"sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4",
|
||||||
"sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2",
|
"sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce",
|
||||||
"sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593",
|
"sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab",
|
||||||
"sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39",
|
"sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a",
|
||||||
"sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389",
|
"sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f",
|
||||||
"sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf",
|
"sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c",
|
||||||
"sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf",
|
"sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9",
|
||||||
"sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89",
|
"sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf",
|
||||||
"sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c",
|
"sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d",
|
||||||
"sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c",
|
"sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627",
|
||||||
"sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f",
|
"sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d",
|
||||||
"sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440",
|
"sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4",
|
||||||
"sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465",
|
"sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c",
|
||||||
"sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136",
|
"sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d",
|
||||||
"sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b",
|
"sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad",
|
||||||
"sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8",
|
"sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b",
|
||||||
"sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3",
|
"sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33",
|
||||||
"sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8",
|
"sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371",
|
||||||
"sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6",
|
"sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1",
|
||||||
"sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e",
|
"sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393",
|
||||||
"sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f",
|
"sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106",
|
||||||
"sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c",
|
"sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df",
|
||||||
"sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e",
|
"sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379",
|
||||||
"sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8",
|
"sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451",
|
||||||
"sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2",
|
"sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b",
|
||||||
"sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020",
|
"sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575",
|
||||||
"sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35",
|
"sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed",
|
||||||
"sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d",
|
"sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb",
|
||||||
"sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3",
|
"sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"
|
||||||
"sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537",
|
|
||||||
"sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809",
|
|
||||||
"sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d",
|
|
||||||
"sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a",
|
|
||||||
"sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"
|
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.16.0"
|
"version": "==1.17.0"
|
||||||
},
|
},
|
||||||
"zstandard": {
|
"zstandard": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
16
src-ui/package-lock.json
generated
16
src-ui/package-lock.json
generated
@ -33,6 +33,7 @@
|
|||||||
"ngx-ui-tour-ng-bootstrap": "^15.0.0",
|
"ngx-ui-tour-ng-bootstrap": "^15.0.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
|
"utif": "^3.1.0",
|
||||||
"uuid": "^11.0.2",
|
"uuid": "^11.0.2",
|
||||||
"zone.js": "^0.14.8"
|
"zone.js": "^0.14.8"
|
||||||
},
|
},
|
||||||
@ -13758,6 +13759,12 @@
|
|||||||
"node": "^16.14.0 || >=18.0.0"
|
"node": "^16.14.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@ -16563,6 +16570,15 @@
|
|||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/utif": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/utif/-/utif-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-WEo4D/xOvFW53K5f5QTaTbbiORcm2/pCL9P6qmJnup+17eYfKaEhDeX9PeQkuyEoIxlbGklDuGl8xwuXYMrrXQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"ngx-ui-tour-ng-bootstrap": "^15.0.0",
|
"ngx-ui-tour-ng-bootstrap": "^15.0.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
|
"utif": "^3.1.0",
|
||||||
"uuid": "^11.0.2",
|
"uuid": "^11.0.2",
|
||||||
"zone.js": "^0.14.8"
|
"zone.js": "^0.14.8"
|
||||||
},
|
},
|
||||||
|
@ -47,14 +47,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@for (document of documentsInTrash; track document.id) {
|
@for (document of documentsInTrash; track document.id) {
|
||||||
<tr (click)="toggleSelected(document); $event.stopPropagation();">
|
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()">
|
||||||
<td>
|
<td>
|
||||||
<div class="form-check m-0 ms-2 me-n2">
|
<div class="form-check m-0 ms-2 me-n2">
|
||||||
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
|
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
|
||||||
<label class="form-check-label" for="{{document.id}}"></label>
|
<label class="form-check-label" for="{{document.id}}"></label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td scope="row">{{ document.title }}</td>
|
<td scope="row">
|
||||||
|
{{ document.title }}
|
||||||
|
<pngx-preview-popup [document]="document" linkClasses="btn btn-sm btn-link" #popupPreview>
|
||||||
|
<i-bs name="eye"></i-bs>
|
||||||
|
</pngx-preview-popup>
|
||||||
|
</td>
|
||||||
<td scope="row" i18n>{{ getDaysRemaining(document) }} days</td>
|
<td scope="row" i18n>{{ getDaysRemaining(document) }} days</td>
|
||||||
<td scope="row">
|
<td scope="row">
|
||||||
<div class="btn-group d-block d-sm-none">
|
<div class="btn-group d-block d-sm-none">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.pdf-viewer-container {
|
.pdf-viewer-container {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
height: 350px;
|
height: 550px;
|
||||||
|
|
||||||
pdf-viewer {
|
pdf-viewer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>{{message}}</p>
|
<p>{{message}}</p>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-8">
|
<div class="col-7">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<div class="input-group-text" i18n>Page</div>
|
<div class="input-group-text" i18n>Page</div>
|
||||||
<input class="form-control" type="number" min="1" [(ngModel)]="page" />
|
<input class="form-control" type="number" min="1" [(ngModel)]="page" />
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</pdf-viewer>
|
</pdf-viewer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-5">
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button class="btn btn-sm btn-primary" (click)="addSplit()" [disabled]="!canSplit">
|
<button class="btn btn-sm btn-primary" (click)="addSplit()" [disabled]="!canSplit">
|
||||||
<i-bs name="plus-circle"></i-bs>
|
<i-bs name="plus-circle"></i-bs>
|
||||||
@ -44,12 +44,12 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch mt-4">
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="form-check form-switch me-auto">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalSwitch" [(ngModel)]="deleteOriginal" [disabled]="!userOwnsDocument">
|
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalSwitch" [(ngModel)]="deleteOriginal" [disabled]="!userOwnsDocument">
|
||||||
<label class="form-check-label" for="deleteOriginalSwitch" i18n>Delete original document after successful split</label>
|
<label class="form-check-label" for="deleteOriginalSwitch" i18n>Delete original document after successful split</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.pdf-viewer-container {
|
.pdf-viewer-container {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
height: 350px;
|
height: 500px;
|
||||||
|
|
||||||
pdf-viewer {
|
pdf-viewer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -38,7 +38,15 @@
|
|||||||
@for (item of selectionModel.items | filter: filterText:'name'; track item; let i = $index) {
|
@for (item of selectionModel.items | filter: filterText:'name'; track item; let i = $index) {
|
||||||
@if (allowSelectNone || item.id) {
|
@if (allowSelectNone || item.id) {
|
||||||
<pngx-toggleable-dropdown-button
|
<pngx-toggleable-dropdown-button
|
||||||
[item]="item" [hideCount]="hideCount(item)" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggled)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i - 1)" [disabled]="disabled">
|
[item]="item"
|
||||||
|
[hideCount]="hideCount(item)"
|
||||||
|
[opacifyCount]="!editing"
|
||||||
|
[state]="selectionModel.get(item.id)"
|
||||||
|
[count]="getUpdatedDocumentCount(item.id)"
|
||||||
|
(toggled)="selectionModel.toggle(item.id)"
|
||||||
|
(exclude)="excludeClicked(item.id)"
|
||||||
|
(click)="setButtonItemIndex(i - 1)"
|
||||||
|
[disabled]="disabled">
|
||||||
</pngx-toggleable-dropdown-button>
|
</pngx-toggleable-dropdown-button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,6 +509,37 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('selection model should sort items by state and document counts, if set', () => {
|
||||||
|
component.items = items.concat([{ id: 4, name: 'Item D' }])
|
||||||
|
component.selectionModel = selectionModel
|
||||||
|
component.documentCounts = [
|
||||||
|
{ id: 1, document_count: 0 }, // Tag1
|
||||||
|
{ id: 2, document_count: 1 }, // Tag2
|
||||||
|
{ id: 4, document_count: 2 },
|
||||||
|
]
|
||||||
|
component.selectionModel.apply()
|
||||||
|
expect(selectionModel.items).toEqual([
|
||||||
|
nullItem,
|
||||||
|
{ id: 4, name: 'Item D' },
|
||||||
|
items[1], // Tag2
|
||||||
|
items[0], // Tag1
|
||||||
|
])
|
||||||
|
|
||||||
|
selectionModel.toggle(items[1].id)
|
||||||
|
component.documentCounts = [
|
||||||
|
{ id: 1, document_count: 0 },
|
||||||
|
{ id: 2, document_count: 1 },
|
||||||
|
{ id: 4, document_count: 0 },
|
||||||
|
]
|
||||||
|
selectionModel.apply()
|
||||||
|
expect(selectionModel.items).toEqual([
|
||||||
|
nullItem,
|
||||||
|
items[1], // Tag2
|
||||||
|
{ id: 4, name: 'Item D' },
|
||||||
|
items[0], // Tag1
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('should set support create, keep open model and call createRef method', fakeAsync(() => {
|
it('should set support create, keep open model and call createRef method', fakeAsync(() => {
|
||||||
component.items = items
|
component.items = items
|
||||||
component.icon = 'tag-fill'
|
component.icon = 'tag-fill'
|
||||||
|
@ -43,6 +43,11 @@ export class FilterableDropdownSelectionModel {
|
|||||||
private _intersection: Intersection = Intersection.Include
|
private _intersection: Intersection = Intersection.Include
|
||||||
temporaryIntersection: Intersection = this._intersection
|
temporaryIntersection: Intersection = this._intersection
|
||||||
|
|
||||||
|
private _documentCounts: SelectionDataItem[] = []
|
||||||
|
public set documentCounts(counts: SelectionDataItem[]) {
|
||||||
|
this._documentCounts = counts
|
||||||
|
}
|
||||||
|
|
||||||
private _items: MatchingModel[] = []
|
private _items: MatchingModel[] = []
|
||||||
get items(): MatchingModel[] {
|
get items(): MatchingModel[] {
|
||||||
return this._items
|
return this._items
|
||||||
@ -69,6 +74,16 @@ export class FilterableDropdownSelectionModel {
|
|||||||
this.getNonTemporary(b.id) == ToggleableItemState.NotSelected
|
this.getNonTemporary(b.id) == ToggleableItemState.NotSelected
|
||||||
) {
|
) {
|
||||||
return -1
|
return -1
|
||||||
|
} else if (
|
||||||
|
this._documentCounts.length &&
|
||||||
|
this.getDocumentCount(a.id) > this.getDocumentCount(b.id)
|
||||||
|
) {
|
||||||
|
return -1
|
||||||
|
} else if (
|
||||||
|
this._documentCounts.length &&
|
||||||
|
this.getDocumentCount(a.id) < this.getDocumentCount(b.id)
|
||||||
|
) {
|
||||||
|
return 1
|
||||||
} else {
|
} else {
|
||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
}
|
}
|
||||||
@ -286,6 +301,10 @@ export class FilterableDropdownSelectionModel {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDocumentCount(id: number) {
|
||||||
|
return this._documentCounts.find((c) => c.id === id)?.document_count
|
||||||
|
}
|
||||||
|
|
||||||
init(map: Map<number, ToggleableItemState>) {
|
init(map: Map<number, ToggleableItemState>) {
|
||||||
this.temporarySelectionStates = map
|
this.temporarySelectionStates = map
|
||||||
this.apply()
|
this.apply()
|
||||||
@ -431,7 +450,11 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
documentCounts: SelectionDataItem[]
|
set documentCounts(counts: SelectionDataItem[]) {
|
||||||
|
if (counts) {
|
||||||
|
this.selectionModel.documentCounts = counts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
shortcutKey: string
|
shortcutKey: string
|
||||||
@ -544,9 +567,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUpdatedDocumentCount(id: number) {
|
getUpdatedDocumentCount(id: number) {
|
||||||
if (this.documentCounts) {
|
return this.selectionModel.getDocumentCount(id)
|
||||||
return this.documentCounts.find((c) => c.id === id)?.document_count
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listKeyDown(event: KeyboardEvent) {
|
listKeyDown(event: KeyboardEvent) {
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="toggleItem($event)" [disabled]="disabled">
|
<button
|
||||||
|
class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom"
|
||||||
|
[class.opacity-50]="opacifyCount && !hideCount && currentCount === 0"
|
||||||
|
role="menuitem"
|
||||||
|
(click)="toggleItem($event)"
|
||||||
|
[disabled]="disabled">
|
||||||
<div class="selected-icon me-1">
|
<div class="selected-icon me-1">
|
||||||
@if (isChecked()) {
|
@if (isChecked()) {
|
||||||
<i-bs width="1em" height="1em" name="check"></i-bs>
|
<i-bs width="1em" height="1em" name="check"></i-bs>
|
||||||
@ -18,6 +23,6 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (!hideCount) {
|
@if (!hideCount) {
|
||||||
<div class="badge bg-light text-dark rounded-pill ms-auto me-1">{{count ?? item.document_count}}</div>
|
<div class="badge bg-light text-dark rounded-pill ms-auto me-1">{{currentCount}}</div>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
@ -29,6 +29,9 @@ export class ToggleableDropdownButtonComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
hideCount: boolean = false
|
hideCount: boolean = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
opacifyCount: boolean = true
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
toggled = new EventEmitter()
|
toggled = new EventEmitter()
|
||||||
|
|
||||||
@ -39,6 +42,10 @@ export class ToggleableDropdownButtonComponent {
|
|||||||
return 'is_inbox_tag' in this.item
|
return 'is_inbox_tag' in this.item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentCount(): number {
|
||||||
|
return this.count ?? this.item.document_count
|
||||||
|
}
|
||||||
|
|
||||||
toggleItem(event: MouseEvent): void {
|
toggleItem(event: MouseEvent): void {
|
||||||
if (this.state == ToggleableItemState.Selected) {
|
if (this.state == ToggleableItemState.Selected) {
|
||||||
this.exclude.emit()
|
this.exclude.emit()
|
||||||
|
@ -1,30 +1,37 @@
|
|||||||
<div class="preview-popup-container">
|
<a [href]="link ?? previewUrl" class="{{linkClasses}}" [target]="linkTarget" [title]="linkTitle"
|
||||||
@if (error) {
|
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle" container="body"
|
||||||
<div class="w-100 h-100 position-relative">
|
autoClose="true" [popoverClass]="popoverClass" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||||
<p class="fst-italic position-absolute top-50 start-50 translate-middle" i18n>Error loading preview</p>
|
<ng-content></ng-content>
|
||||||
</div>
|
</a>
|
||||||
} @else {
|
<ng-template #previewContent>
|
||||||
@if (renderAsObject) {
|
<div class="preview-popup-container">
|
||||||
@if (previewText) {
|
@if (error) {
|
||||||
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
<div class="w-100 h-100 position-relative">
|
||||||
} @else {
|
<p class="fst-italic position-absolute top-50 start-50 translate-middle" i18n>Error loading preview</p>
|
||||||
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
</div>
|
||||||
}
|
|
||||||
} @else {
|
} @else {
|
||||||
@if (requiresPassword) {
|
@if (renderAsObject) {
|
||||||
<div class="w-100 h-100 position-relative">
|
@if (previewText) {
|
||||||
<i-bs width="2em" height="2em" class="position-absolute top-50 start-50 translate-middle" name="file-earmark-lock"></i-bs>
|
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
||||||
</div>
|
} @else {
|
||||||
}
|
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
||||||
@if (!requiresPassword) {
|
}
|
||||||
<pdf-viewer
|
} @else {
|
||||||
[src]="previewURL"
|
@if (requiresPassword) {
|
||||||
[original-size]="false"
|
<div class="w-100 h-100 position-relative">
|
||||||
[show-borders]="false"
|
<i-bs width="2em" height="2em" class="position-absolute top-50 start-50 translate-middle" name="file-earmark-lock"></i-bs>
|
||||||
[show-all]="true"
|
</div>
|
||||||
(error)="onError($event)">
|
}
|
||||||
</pdf-viewer>
|
@if (!requiresPassword) {
|
||||||
|
<pdf-viewer
|
||||||
|
[src]="previewURL"
|
||||||
|
[original-size]="false"
|
||||||
|
[show-borders]="false"
|
||||||
|
[show-all]="true"
|
||||||
|
(error)="onError($event)">
|
||||||
|
</pdf-viewer>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</ng-template>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
fakeAsync,
|
||||||
|
TestBed,
|
||||||
|
tick,
|
||||||
|
} from '@angular/core/testing'
|
||||||
|
|
||||||
import { PreviewPopupComponent } from './preview-popup.component'
|
import { PreviewPopupComponent } from './preview-popup.component'
|
||||||
import { By } from '@angular/platform-browser'
|
import { By } from '@angular/platform-browser'
|
||||||
@ -15,6 +20,8 @@ import {
|
|||||||
withInterceptorsFromDi,
|
withInterceptorsFromDi,
|
||||||
} from '@angular/common/http'
|
} from '@angular/common/http'
|
||||||
import { of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
|
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
|
|
||||||
const doc = {
|
const doc = {
|
||||||
id: 10,
|
id: 10,
|
||||||
@ -34,8 +41,12 @@ describe('PreviewPopupComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [PreviewPopupComponent, SafeUrlPipe],
|
declarations: [PreviewPopupComponent, SafeUrlPipe, DocumentTitlePipe],
|
||||||
imports: [NgxBootstrapIconsModule.pick(allIcons), PdfViewerModule],
|
imports: [
|
||||||
|
NgxBootstrapIconsModule.pick(allIcons),
|
||||||
|
PdfViewerModule,
|
||||||
|
NgbPopoverModule,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
provideHttpClientTesting(),
|
provideHttpClientTesting(),
|
||||||
@ -70,12 +81,14 @@ describe('PreviewPopupComponent', () => {
|
|||||||
|
|
||||||
it('should render object if native PDF viewer enabled', () => {
|
it('should render object if native PDF viewer enabled', () => {
|
||||||
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, true)
|
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, true)
|
||||||
|
component.popover.open()
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render pngx viewer if native PDF viewer disabled', () => {
|
it('should render pngx viewer if native PDF viewer disabled', () => {
|
||||||
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
|
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
|
||||||
|
component.popover.open()
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.query(By.css('object'))).toBeNull()
|
expect(fixture.debugElement.query(By.css('object'))).toBeNull()
|
||||||
expect(fixture.debugElement.query(By.css('pdf-viewer'))).not.toBeNull()
|
expect(fixture.debugElement.query(By.css('pdf-viewer'))).not.toBeNull()
|
||||||
@ -83,6 +96,7 @@ describe('PreviewPopupComponent', () => {
|
|||||||
|
|
||||||
it('should show lock icon on password error', () => {
|
it('should show lock icon on password error', () => {
|
||||||
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
|
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
|
||||||
|
component.popover.open()
|
||||||
component.onError({ name: 'PasswordException' })
|
component.onError({ name: 'PasswordException' })
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.requiresPassword).toBeTruthy()
|
expect(component.requiresPassword).toBeTruthy()
|
||||||
@ -93,16 +107,18 @@ describe('PreviewPopupComponent', () => {
|
|||||||
component.document.original_file_name = 'sample.png'
|
component.document.original_file_name = 'sample.png'
|
||||||
component.document.mime_type = 'image/png'
|
component.document.mime_type = 'image/png'
|
||||||
component.document.archived_file_name = undefined
|
component.document.archived_file_name = undefined
|
||||||
|
component.popover.open()
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show message on error', () => {
|
it('should show message on error', () => {
|
||||||
|
component.popover.open()
|
||||||
component.onError({})
|
component.onError({})
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
expect(
|
||||||
'Error loading preview'
|
fixture.debugElement.query(By.css('.popover')).nativeElement.textContent
|
||||||
)
|
).toContain('Error loading preview')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get text content from http if appropriate', () => {
|
it('should get text content from http if appropriate', () => {
|
||||||
@ -122,4 +138,17 @@ describe('PreviewPopupComponent', () => {
|
|||||||
component.init()
|
component.init()
|
||||||
expect(component.previewText).toEqual('Preview text')
|
expect(component.previewText).toEqual('Preview text')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
||||||
|
component.mouseEnterPreview()
|
||||||
|
expect(component.popover.isOpen()).toBeTruthy()
|
||||||
|
tick(600)
|
||||||
|
component.close()
|
||||||
|
|
||||||
|
component.mouseEnterPreview()
|
||||||
|
tick(100)
|
||||||
|
component.mouseLeavePreview()
|
||||||
|
tick(600)
|
||||||
|
expect(component.popover.isOpen()).toBeFalsy()
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Component, Input, OnDestroy } from '@angular/core'
|
import { Component, Input, OnDestroy, ViewChild } from '@angular/core'
|
||||||
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { first, Subject, takeUntil } from 'rxjs'
|
import { first, Subject, takeUntil } from 'rxjs'
|
||||||
import { Document } from 'src/app/data/document'
|
import { Document } from 'src/app/data/document'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
@ -23,6 +24,18 @@ export class PreviewPopupComponent implements OnDestroy {
|
|||||||
return this._document
|
return this._document
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
link: string
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
linkClasses: string = 'btn btn-sm btn-outline-secondary'
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
linkTarget: string = '_blank'
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
linkTitle: string = $localize`Open preview`
|
||||||
|
|
||||||
unsubscribeNotifier: Subject<any> = new Subject()
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
error = false
|
error = false
|
||||||
@ -31,6 +44,12 @@ export class PreviewPopupComponent implements OnDestroy {
|
|||||||
|
|
||||||
previewText: string
|
previewText: string
|
||||||
|
|
||||||
|
@ViewChild('popover') popover: NgbPopover
|
||||||
|
|
||||||
|
mouseOnPreview: boolean
|
||||||
|
|
||||||
|
popoverClass: string = 'shadow popover-preview'
|
||||||
|
|
||||||
get renderAsObject(): boolean {
|
get renderAsObject(): boolean {
|
||||||
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
|
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
|
||||||
}
|
}
|
||||||
@ -83,4 +102,33 @@ export class PreviewPopupComponent implements OnDestroy {
|
|||||||
this.error = true
|
this.error = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get previewUrl() {
|
||||||
|
return this.documentService.getPreviewUrl(this.document.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEnterPreview() {
|
||||||
|
this.mouseOnPreview = true
|
||||||
|
if (!this.popover.isOpen()) {
|
||||||
|
// we're going to open but hide to pre-load content during hover delay
|
||||||
|
this.popover.open()
|
||||||
|
this.popoverClass = 'shadow popover-preview pe-none opacity-0'
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.mouseOnPreview) {
|
||||||
|
// show popover
|
||||||
|
this.popoverClass = this.popoverClass.replace('pe-none opacity-0', '')
|
||||||
|
} else {
|
||||||
|
this.popover.close()
|
||||||
|
}
|
||||||
|
}, 600)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseLeavePreview() {
|
||||||
|
this.mouseOnPreview = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
this.popover.close(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,6 +388,15 @@
|
|||||||
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
|
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@case (ContentRenderType.TIFF) {
|
||||||
|
@if (!tiffError) {
|
||||||
|
<div class="preview-sticky">
|
||||||
|
<img [src]="tiffURL" width="100%" height="100%" alt="{{title}}" />
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="preview-sticky bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{tiffError}}</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@case (ContentRenderType.Other) {
|
@case (ContentRenderType.Other) {
|
||||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ textarea.rtl {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
object-position: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumb-preview {
|
.thumb-preview {
|
||||||
|
@ -1270,4 +1270,46 @@ describe('DocumentDetailComponent', () => {
|
|||||||
expect(component.createDisabled(DataType.StoragePath)).toBeFalsy()
|
expect(component.createDisabled(DataType.StoragePath)).toBeFalsy()
|
||||||
expect(component.createDisabled(DataType.Tag)).toBeFalsy()
|
expect(component.createDisabled(DataType.Tag)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should call tryRenderTiff when no archive and file is tiff', () => {
|
||||||
|
initNormally()
|
||||||
|
const tiffRenderSpy = jest.spyOn(
|
||||||
|
DocumentDetailComponent.prototype as any,
|
||||||
|
'tryRenderTiff'
|
||||||
|
)
|
||||||
|
const doc = Object.assign({}, component.document)
|
||||||
|
doc.archived_file_name = null
|
||||||
|
doc.mime_type = 'image/tiff'
|
||||||
|
jest
|
||||||
|
.spyOn(documentService, 'getMetadata')
|
||||||
|
.mockReturnValue(
|
||||||
|
of({ has_archive_version: false, original_mime_type: 'image/tiff' })
|
||||||
|
)
|
||||||
|
component.updateComponent(doc)
|
||||||
|
fixture.detectChanges()
|
||||||
|
expect(component.archiveContentRenderType).toEqual(
|
||||||
|
component.ContentRenderType.TIFF
|
||||||
|
)
|
||||||
|
expect(tiffRenderSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should try to render tiff and show error if failed', () => {
|
||||||
|
initNormally()
|
||||||
|
// just the text request
|
||||||
|
httpTestingController.expectOne(component.previewUrl)
|
||||||
|
|
||||||
|
// invalid tiff
|
||||||
|
component['tryRenderTiff']()
|
||||||
|
httpTestingController
|
||||||
|
.expectOne(component.previewUrl)
|
||||||
|
.flush(new ArrayBuffer(100)) // arraybuffer
|
||||||
|
expect(component.tiffError).not.toBeUndefined()
|
||||||
|
|
||||||
|
// http error
|
||||||
|
component['tryRenderTiff']()
|
||||||
|
httpTestingController
|
||||||
|
.expectOne(component.previewUrl)
|
||||||
|
.error(new ErrorEvent('failed'))
|
||||||
|
expect(component.tiffError).not.toBeUndefined()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -72,6 +72,7 @@ import { DeletePagesConfirmDialogComponent } from '../common/confirm-dialog/dele
|
|||||||
import { HotKeyService } from 'src/app/services/hot-key.service'
|
import { HotKeyService } from 'src/app/services/hot-key.service'
|
||||||
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
|
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
|
||||||
import { DataType } from 'src/app/data/datatype'
|
import { DataType } from 'src/app/data/datatype'
|
||||||
|
import * as UTIF from 'utif'
|
||||||
|
|
||||||
enum DocumentDetailNavIDs {
|
enum DocumentDetailNavIDs {
|
||||||
Details = 1,
|
Details = 1,
|
||||||
@ -89,6 +90,7 @@ enum ContentRenderType {
|
|||||||
Text = 'text',
|
Text = 'text',
|
||||||
Other = 'other',
|
Other = 'other',
|
||||||
Unknown = 'unknown',
|
Unknown = 'unknown',
|
||||||
|
TIFF = 'tiff',
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ZoomSetting {
|
enum ZoomSetting {
|
||||||
@ -136,6 +138,8 @@ export class DocumentDetailComponent
|
|||||||
downloadUrl: string
|
downloadUrl: string
|
||||||
downloadOriginalUrl: string
|
downloadOriginalUrl: string
|
||||||
previewLoaded: boolean = false
|
previewLoaded: boolean = false
|
||||||
|
tiffURL: string
|
||||||
|
tiffError: string
|
||||||
|
|
||||||
correspondents: Correspondent[]
|
correspondents: Correspondent[]
|
||||||
documentTypes: DocumentType[]
|
documentTypes: DocumentType[]
|
||||||
@ -244,6 +248,8 @@ export class DocumentDetailComponent
|
|||||||
['text/plain', 'application/csv', 'text/csv'].includes(mimeType)
|
['text/plain', 'application/csv', 'text/csv'].includes(mimeType)
|
||||||
) {
|
) {
|
||||||
return ContentRenderType.Text
|
return ContentRenderType.Text
|
||||||
|
} else if (mimeType.indexOf('tiff') >= 0) {
|
||||||
|
return ContentRenderType.TIFF
|
||||||
} else if (mimeType?.indexOf('image/') === 0) {
|
} else if (mimeType?.indexOf('image/') === 0) {
|
||||||
return ContentRenderType.Image
|
return ContentRenderType.Image
|
||||||
}
|
}
|
||||||
@ -542,6 +548,9 @@ export class DocumentDetailComponent
|
|||||||
this.document = doc
|
this.document = doc
|
||||||
this.requiresPassword = false
|
this.requiresPassword = false
|
||||||
this.updateFormForCustomFields()
|
this.updateFormForCustomFields()
|
||||||
|
if (this.archiveContentRenderType === ContentRenderType.TIFF) {
|
||||||
|
this.tryRenderTiff()
|
||||||
|
}
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.getMetadata(doc.id)
|
.getMetadata(doc.id)
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -721,6 +730,7 @@ export class DocumentDetailComponent
|
|||||||
|
|
||||||
save(close: boolean = false) {
|
save(close: boolean = false) {
|
||||||
this.networkActive = true
|
this.networkActive = true
|
||||||
|
;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change'))
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.update(this.document)
|
.update(this.document)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
@ -1163,6 +1173,7 @@ export class DocumentDetailComponent
|
|||||||
splitDocument() {
|
splitDocument() {
|
||||||
let modal = this.modalService.open(SplitConfirmDialogComponent, {
|
let modal = this.modalService.open(SplitConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
|
size: 'lg',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Split confirm`
|
modal.componentInstance.title = $localize`Split confirm`
|
||||||
modal.componentInstance.messageBold = $localize`This operation will split the selected document(s) into new documents.`
|
modal.componentInstance.messageBold = $localize`This operation will split the selected document(s) into new documents.`
|
||||||
@ -1201,6 +1212,7 @@ export class DocumentDetailComponent
|
|||||||
rotateDocument() {
|
rotateDocument() {
|
||||||
let modal = this.modalService.open(RotateConfirmDialogComponent, {
|
let modal = this.modalService.open(RotateConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
|
size: 'lg',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Rotate confirm`
|
modal.componentInstance.title = $localize`Rotate confirm`
|
||||||
modal.componentInstance.messageBold = $localize`This operation will permanently rotate the original version of the current document.`
|
modal.componentInstance.messageBold = $localize`This operation will permanently rotate the original version of the current document.`
|
||||||
@ -1275,4 +1287,45 @@ export class DocumentDetailComponent
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private tryRenderTiff() {
|
||||||
|
this.http.get(this.previewUrl, { responseType: 'arraybuffer' }).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
try {
|
||||||
|
// See UTIF.js > _imgLoaded
|
||||||
|
const tiffIfds: any[] = UTIF.decode(res)
|
||||||
|
var vsns = tiffIfds,
|
||||||
|
ma = 0,
|
||||||
|
page = vsns[0]
|
||||||
|
if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD)
|
||||||
|
for (var i = 0; i < vsns.length; i++) {
|
||||||
|
var img = vsns[i]
|
||||||
|
if (img['t258'] == null || img['t258'].length < 3) continue
|
||||||
|
var ar = img['t256'] * img['t257']
|
||||||
|
if (ar > ma) {
|
||||||
|
ma = ar
|
||||||
|
page = img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UTIF.decodeImage(res, page, tiffIfds)
|
||||||
|
const rgba = UTIF.toRGBA8(page)
|
||||||
|
const { width: w, height: h } = page
|
||||||
|
var cnv = document.createElement('canvas')
|
||||||
|
cnv.width = w
|
||||||
|
cnv.height = h
|
||||||
|
var ctx = cnv.getContext('2d'),
|
||||||
|
imgd = ctx.createImageData(w, h)
|
||||||
|
for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i]
|
||||||
|
ctx.putImageData(imgd, 0, 0)
|
||||||
|
this.tiffURL = cnv.toDataURL()
|
||||||
|
} catch (err) {
|
||||||
|
this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,11 +782,11 @@ export class BulkEditorComponent
|
|||||||
rotateSelected() {
|
rotateSelected() {
|
||||||
let modal = this.modalService.open(RotateConfirmDialogComponent, {
|
let modal = this.modalService.open(RotateConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
|
size: 'lg',
|
||||||
})
|
})
|
||||||
const rotateDialog = modal.componentInstance as RotateConfirmDialogComponent
|
const rotateDialog = modal.componentInstance as RotateConfirmDialogComponent
|
||||||
rotateDialog.title = $localize`Rotate confirm`
|
rotateDialog.title = $localize`Rotate confirm`
|
||||||
rotateDialog.messageBold = $localize`This operation will permanently rotate the original version of ${this.list.selected.size} document(s).`
|
rotateDialog.messageBold = $localize`This operation will permanently rotate the original version of ${this.list.selected.size} document(s).`
|
||||||
rotateDialog.message = $localize`This will alter the original copy.`
|
|
||||||
rotateDialog.btnClass = 'btn-danger'
|
rotateDialog.btnClass = 'btn-danger'
|
||||||
rotateDialog.btnCaption = $localize`Proceed`
|
rotateDialog.btnCaption = $localize`Proceed`
|
||||||
rotateDialog.documentID = Array.from(this.list.selected)[0]
|
rotateDialog.documentID = Array.from(this.list.selected)[0]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="card mb-3 shadow-sm bg-light" [class.card-selected]="selected" [class.document-card]="selectable" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()">
|
<div class="card mb-3 shadow-sm bg-light" [class.card-selected]="selected" [class.document-card]="selectable" (mouseleave)="mouseLeaveCard()">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-md-2 doc-img-container rounded-start" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit()">
|
<div class="col-md-2 doc-img-container rounded-start" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit()">
|
||||||
<img [src]="getThumbUrl()" class="card-img doc-img border-end rounded-start" [class.inverted]="getIsThumbInverted()">
|
<img [src]="getThumbUrl()" class="card-img doc-img border-end rounded-start" [class.inverted]="getIsThumbInverted()">
|
||||||
@ -56,14 +56,9 @@
|
|||||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||||
<i-bs name="file-earmark-richtext"></i-bs> <span class="d-none d-md-inline" i18n>Open</span>
|
<i-bs name="file-earmark-richtext"></i-bs> <span class="d-none d-md-inline" i18n>Open</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-sm btn-outline-secondary" target="_blank" [href]="previewUrl"
|
<pngx-preview-popup [document]="document" #popupPreview>
|
||||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
|
||||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
|
||||||
<i-bs name="eye"></i-bs> <span class="d-none d-md-inline" i18n>View</span>
|
<i-bs name="eye"></i-bs> <span class="d-none d-md-inline" i18n>View</span>
|
||||||
</a>
|
</pngx-preview-popup>
|
||||||
<ng-template #previewContent>
|
|
||||||
<pngx-preview-popup [document]="document"></pngx-preview-popup>
|
|
||||||
</ng-template>
|
|
||||||
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
|
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
|
||||||
<i-bs name="download"></i-bs> <span class="d-none d-md-inline" i18n>Download</span>
|
<i-bs name="download"></i-bs> <span class="d-none d-md-inline" i18n>Download</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { DatePipe } from '@angular/common'
|
import { DatePipe } from '@angular/common'
|
||||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||||
import {
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
ComponentFixture,
|
|
||||||
TestBed,
|
|
||||||
fakeAsync,
|
|
||||||
tick,
|
|
||||||
} from '@angular/core/testing'
|
|
||||||
import { By } from '@angular/platform-browser'
|
import { By } from '@angular/platform-browser'
|
||||||
import { RouterTestingModule } from '@angular/router/testing'
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
import {
|
import {
|
||||||
@ -84,21 +79,6 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
expect(fixture.nativeElement.textContent).toContain('8 pages')
|
expect(fixture.nativeElement.textContent).toContain('8 pages')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
|
||||||
component.mouseEnterPreview()
|
|
||||||
expect(component.popover.isOpen()).toBeTruthy()
|
|
||||||
expect(component.popoverHidden).toBeTruthy()
|
|
||||||
tick(600)
|
|
||||||
expect(component.popoverHidden).toBeFalsy()
|
|
||||||
component.mouseLeaveCard()
|
|
||||||
|
|
||||||
component.mouseEnterPreview()
|
|
||||||
tick(100)
|
|
||||||
component.mouseLeavePreview()
|
|
||||||
tick(600)
|
|
||||||
expect(component.popover.isOpen()).toBeFalsy()
|
|
||||||
}))
|
|
||||||
|
|
||||||
it('should trim content', () => {
|
it('should trim content', () => {
|
||||||
expect(component.contentTrimmed).toHaveLength(503) // includes ...
|
expect(component.contentTrimmed).toHaveLength(503) // includes ...
|
||||||
})
|
})
|
||||||
|
@ -12,9 +12,9 @@ import {
|
|||||||
} from 'src/app/data/document'
|
} from 'src/app/data/document'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-document-card-large',
|
selector: 'pngx-document-card-large',
|
||||||
@ -65,7 +65,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
|||||||
@Output()
|
@Output()
|
||||||
clickMoreLike = new EventEmitter()
|
clickMoreLike = new EventEmitter()
|
||||||
|
|
||||||
@ViewChild('popover') popover: NgbPopover
|
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
||||||
|
|
||||||
mouseOnPreview = false
|
mouseOnPreview = false
|
||||||
popoverHidden = true
|
popoverHidden = true
|
||||||
@ -112,29 +112,8 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
|||||||
return this.documentService.getPreviewUrl(this.document.id)
|
return this.documentService.getPreviewUrl(this.document.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseEnterPreview() {
|
|
||||||
this.mouseOnPreview = true
|
|
||||||
if (!this.popover.isOpen()) {
|
|
||||||
// we're going to open but hide to pre-load content during hover delay
|
|
||||||
this.popover.open()
|
|
||||||
this.popoverHidden = true
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.mouseOnPreview) {
|
|
||||||
// show popover
|
|
||||||
this.popoverHidden = false
|
|
||||||
} else {
|
|
||||||
this.popover.close()
|
|
||||||
}
|
|
||||||
}, 600)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseLeavePreview() {
|
|
||||||
this.mouseOnPreview = false
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseLeaveCard() {
|
mouseLeaveCard() {
|
||||||
this.popover.close()
|
this.popupPreview.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
get contentTrimmed() {
|
get contentTrimmed() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="col p-2 h-100">
|
<div class="col p-2 h-100">
|
||||||
<div class="card h-100 shadow-sm document-card" [class.card-selected]="selected" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()">
|
<div class="card h-100 shadow-sm document-card" [class.card-selected]="selected" (mouseleave)="mouseLeaveCard()">
|
||||||
<div class="border-bottom doc-img-container rounded-top" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit(this)">
|
<div class="border-bottom doc-img-container rounded-top" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit(this)">
|
||||||
<img class="card-img doc-img" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()">
|
<img class="card-img doc-img" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()">
|
||||||
|
|
||||||
@ -129,14 +129,9 @@
|
|||||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Open" i18n-title *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
|
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Open" i18n-title *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
|
||||||
<i-bs name="file-earmark-richtext"></i-bs>
|
<i-bs name="file-earmark-richtext"></i-bs>
|
||||||
</a>
|
</a>
|
||||||
<a [href]="previewUrl" target="_blank" class="btn btn-sm btn-outline-secondary"
|
<pngx-preview-popup [document]="document" #popupPreview>
|
||||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
|
||||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
|
||||||
<i-bs name="eye"></i-bs>
|
<i-bs name="eye"></i-bs>
|
||||||
</a>
|
</pngx-preview-popup>
|
||||||
<ng-template #previewContent>
|
|
||||||
<pngx-preview-popup [document]="document"></pngx-preview-popup>
|
|
||||||
</ng-template>
|
|
||||||
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" i18n-title (click)="$event.stopPropagation()">
|
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||||
<i-bs name="download"></i-bs>
|
<i-bs name="download"></i-bs>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { DatePipe } from '@angular/common'
|
import { DatePipe } from '@angular/common'
|
||||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||||
import {
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
ComponentFixture,
|
|
||||||
TestBed,
|
|
||||||
fakeAsync,
|
|
||||||
tick,
|
|
||||||
} from '@angular/core/testing'
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing'
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
import {
|
import {
|
||||||
NgbPopoverModule,
|
NgbPopoverModule,
|
||||||
@ -116,19 +111,4 @@ describe('DocumentCardSmallComponent', () => {
|
|||||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||||
).toHaveLength(6)
|
).toHaveLength(6)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
|
||||||
component.mouseEnterPreview()
|
|
||||||
expect(component.popover.isOpen()).toBeTruthy()
|
|
||||||
expect(component.popoverHidden).toBeTruthy()
|
|
||||||
tick(600)
|
|
||||||
expect(component.popoverHidden).toBeFalsy()
|
|
||||||
component.mouseLeaveCard()
|
|
||||||
|
|
||||||
component.mouseEnterPreview()
|
|
||||||
tick(100)
|
|
||||||
component.mouseLeavePreview()
|
|
||||||
tick(600)
|
|
||||||
expect(component.popover.isOpen()).toBeFalsy()
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
|
@ -13,9 +13,9 @@ import {
|
|||||||
} from 'src/app/data/document'
|
} from 'src/app/data/document'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-document-card-small',
|
selector: 'pngx-document-card-small',
|
||||||
@ -61,10 +61,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
|||||||
|
|
||||||
moreTags: number = null
|
moreTags: number = null
|
||||||
|
|
||||||
@ViewChild('popover') popover: NgbPopover
|
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
||||||
|
|
||||||
mouseOnPreview = false
|
|
||||||
popoverHidden = true
|
|
||||||
|
|
||||||
getIsThumbInverted() {
|
getIsThumbInverted() {
|
||||||
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
|
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
|
||||||
@ -78,10 +75,6 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
|||||||
return this.documentService.getDownloadUrl(this.document.id)
|
return this.documentService.getDownloadUrl(this.document.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
get previewUrl() {
|
|
||||||
return this.documentService.getPreviewUrl(this.document.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
get privateName() {
|
get privateName() {
|
||||||
return $localize`Private`
|
return $localize`Private`
|
||||||
}
|
}
|
||||||
@ -100,29 +93,8 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseEnterPreview() {
|
|
||||||
this.mouseOnPreview = true
|
|
||||||
if (!this.popover.isOpen()) {
|
|
||||||
// we're going to open but hide to pre-load content during hover delay
|
|
||||||
this.popover.open()
|
|
||||||
this.popoverHidden = true
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.mouseOnPreview) {
|
|
||||||
// show popover
|
|
||||||
this.popoverHidden = false
|
|
||||||
} else {
|
|
||||||
this.popover.close()
|
|
||||||
}
|
|
||||||
}, 600)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseLeavePreview() {
|
|
||||||
this.mouseOnPreview = false
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseLeaveCard() {
|
mouseLeaveCard() {
|
||||||
this.popover.close()
|
this.popupPreview.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
get notesEnabled(): boolean {
|
get notesEnabled(): boolean {
|
||||||
|
@ -292,7 +292,12 @@
|
|||||||
@if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
|
@if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
|
||||||
<td width="30%">
|
<td width="30%">
|
||||||
@if (activeDisplayFields.includes(DisplayField.TITLE)) {
|
@if (activeDisplayFields.includes(DisplayField.TITLE)) {
|
||||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
<div class="d-inline-block" (mouseleave)="popupPreview.close()">
|
||||||
|
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||||
|
<pngx-preview-popup [document]="d" linkClasses="btn btn-sm btn-link text-secondary" linkTitle="Preview document" (click)="$event.stopPropagation()" i18n-linkTitle #popupPreview>
|
||||||
|
<i-bs name="eye"></i-bs>
|
||||||
|
</pngx-preview-popup>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
|
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
|
||||||
@for (t of d.tags$ | async; track t) {
|
@for (t of d.tags$ | async; track t) {
|
||||||
|
@ -72,6 +72,7 @@ import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
|||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
|
import { PreviewPopupComponent } from '../common/preview-popup/preview-popup.component'
|
||||||
|
|
||||||
const docs: Document[] = [
|
const docs: Document[] = [
|
||||||
{
|
{
|
||||||
@ -137,6 +138,7 @@ describe('DocumentListComponent', () => {
|
|||||||
UsernamePipe,
|
UsernamePipe,
|
||||||
SafeHtmlPipe,
|
SafeHtmlPipe,
|
||||||
IsNumberPipe,
|
IsNumberPipe,
|
||||||
|
PreviewPopupComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
RouterTestingModule.withRoutes(routes),
|
RouterTestingModule.withRoutes(routes),
|
||||||
|
@ -17,6 +17,8 @@ export enum GlobalSearchType {
|
|||||||
TITLE_CONTENT = 'title-content',
|
TITLE_CONTENT = 'title-content',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PAPERLESS_GREEN_HEX = '#17541f'
|
||||||
|
|
||||||
export const SETTINGS_KEYS = {
|
export const SETTINGS_KEYS = {
|
||||||
LANGUAGE: 'language',
|
LANGUAGE: 'language',
|
||||||
APP_LOGO: 'app_logo',
|
APP_LOGO: 'app_logo',
|
||||||
|
@ -17,7 +17,12 @@ import {
|
|||||||
hexToHsl,
|
hexToHsl,
|
||||||
} from 'src/app/utils/color'
|
} from 'src/app/utils/color'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import { UiSettings, SETTINGS, SETTINGS_KEYS } from '../data/ui-settings'
|
import {
|
||||||
|
UiSettings,
|
||||||
|
SETTINGS,
|
||||||
|
SETTINGS_KEYS,
|
||||||
|
PAPERLESS_GREEN_HEX,
|
||||||
|
} from '../data/ui-settings'
|
||||||
import { User } from '../data/user'
|
import { User } from '../data/user'
|
||||||
import {
|
import {
|
||||||
PermissionAction,
|
PermissionAction,
|
||||||
@ -420,7 +425,7 @@ export class SettingsService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (themeColor) {
|
if (themeColor?.length) {
|
||||||
const hsl = hexToHsl(themeColor)
|
const hsl = hexToHsl(themeColor)
|
||||||
const bgBrightnessEstimate = estimateBrightnessForColor(themeColor)
|
const bgBrightnessEstimate = estimateBrightnessForColor(themeColor)
|
||||||
|
|
||||||
@ -445,6 +450,11 @@ export class SettingsService {
|
|||||||
document.documentElement.style.removeProperty('--pngx-primary')
|
document.documentElement.style.removeProperty('--pngx-primary')
|
||||||
document.documentElement.style.removeProperty('--pngx-primary-lightness')
|
document.documentElement.style.removeProperty('--pngx-primary-lightness')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.meta.updateTag({
|
||||||
|
name: 'theme-color',
|
||||||
|
content: themeColor?.length ? themeColor : PAPERLESS_GREEN_HEX,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getLanguageOptions(): LanguageOption[] {
|
getLanguageOptions(): LanguageOption[] {
|
||||||
|
@ -564,11 +564,6 @@ table.table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-hidden .popover {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tour
|
// Tour
|
||||||
.tour-active .popover {
|
.tour-active .popover {
|
||||||
min-width: 360px;
|
min-width: 360px;
|
||||||
@ -728,3 +723,27 @@ i-bs svg {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixes for buttons in preview popup
|
||||||
|
.btn-group pngx-preview-popup:not(:last-child) {
|
||||||
|
// Prevent double borders when buttons are next to each other
|
||||||
|
> .btn {
|
||||||
|
margin-left: calc(#{$btn-border-width} * -1);
|
||||||
|
}
|
||||||
|
> .btn {
|
||||||
|
@include border-end-radius(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-group pngx-preview-popup:not(:first-child) {
|
||||||
|
> .btn {
|
||||||
|
@include border-start-radius(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-group pngx-preview-popup {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
> .btn {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@ def settings(request):
|
|||||||
app_logo = (
|
app_logo = (
|
||||||
django_settings.APP_LOGO
|
django_settings.APP_LOGO
|
||||||
if general_config.app_logo is None or len(general_config.app_logo) == 0
|
if general_config.app_logo is None or len(general_config.app_logo) == 0
|
||||||
else general_config.app_logo
|
else django_settings.BASE_URL + general_config.app_logo.lstrip("/")
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from auditlog.models import LogEntry
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import override_settings
|
||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
@ -51,8 +53,12 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
self.doc3.tags.add(self.t2)
|
self.doc3.tags.add(self.t2)
|
||||||
self.doc4.tags.add(self.t1, self.t2)
|
self.doc4.tags.add(self.t1, self.t2)
|
||||||
self.sp1 = StoragePath.objects.create(name="sp1", path="Something/{checksum}")
|
self.sp1 = StoragePath.objects.create(name="sp1", path="Something/{checksum}")
|
||||||
self.cf1 = CustomField.objects.create(name="cf1", data_type="text")
|
self.cf1 = CustomField.objects.create(name="cf1", data_type="string")
|
||||||
self.cf2 = CustomField.objects.create(name="cf2", data_type="text")
|
self.cf2 = CustomField.objects.create(name="cf2", data_type="string")
|
||||||
|
|
||||||
|
def setup_mock(self, m, method_name, return_value="OK"):
|
||||||
|
m.return_value = return_value
|
||||||
|
m.__name__ = method_name
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.bulk_update_documents.delay")
|
@mock.patch("documents.bulk_edit.bulk_update_documents.delay")
|
||||||
def test_api_set_correspondent(self, bulk_update_task_mock):
|
def test_api_set_correspondent(self, bulk_update_task_mock):
|
||||||
@ -178,7 +184,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.modify_tags")
|
@mock.patch("documents.serialisers.bulk_edit.modify_tags")
|
||||||
def test_api_modify_tags(self, m):
|
def test_api_modify_tags(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "modify_tags")
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -211,7 +217,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
- API returns HTTP 400
|
- API returns HTTP 400
|
||||||
- modify_tags is not called
|
- modify_tags is not called
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "modify_tags")
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -230,7 +236,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.modify_custom_fields")
|
@mock.patch("documents.serialisers.bulk_edit.modify_custom_fields")
|
||||||
def test_api_modify_custom_fields(self, m):
|
def test_api_modify_custom_fields(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "modify_custom_fields")
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -263,8 +269,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
- API returns HTTP 400
|
- API returns HTTP 400
|
||||||
- modify_custom_fields is not called
|
- modify_custom_fields is not called
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "modify_custom_fields")
|
||||||
|
|
||||||
# Missing add_custom_fields
|
# Missing add_custom_fields
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
@ -359,7 +364,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.delete")
|
@mock.patch("documents.serialisers.bulk_edit.delete")
|
||||||
def test_api_delete(self, m):
|
def test_api_delete(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "delete")
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -383,8 +388,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- set_storage_path is called with correct document IDs and storage_path ID
|
- set_storage_path is called with correct document IDs and storage_path ID
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "set_storage_path")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -414,8 +418,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- set_storage_path is called with correct document IDs and None storage_path
|
- set_storage_path is called with correct document IDs and None storage_path
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "set_storage_path")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -728,7 +731,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.set_permissions")
|
@mock.patch("documents.serialisers.bulk_edit.set_permissions")
|
||||||
def test_set_permissions(self, m):
|
def test_set_permissions(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "set_permissions")
|
||||||
user1 = User.objects.create(username="user1")
|
user1 = User.objects.create(username="user1")
|
||||||
user2 = User.objects.create(username="user2")
|
user2 = User.objects.create(username="user2")
|
||||||
permissions = {
|
permissions = {
|
||||||
@ -763,7 +766,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.set_permissions")
|
@mock.patch("documents.serialisers.bulk_edit.set_permissions")
|
||||||
def test_set_permissions_merge(self, m):
|
def test_set_permissions_merge(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "set_permissions")
|
||||||
user1 = User.objects.create(username="user1")
|
user1 = User.objects.create(username="user1")
|
||||||
user2 = User.objects.create(username="user2")
|
user2 = User.objects.create(username="user2")
|
||||||
permissions = {
|
permissions = {
|
||||||
@ -823,7 +826,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- User is not able to change permissions
|
- User is not able to change permissions
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "set_permissions")
|
||||||
self.doc1.owner = User.objects.get(username="temp_admin")
|
self.doc1.owner = User.objects.get(username="temp_admin")
|
||||||
self.doc1.save()
|
self.doc1.save()
|
||||||
user1 = User.objects.create(username="user1")
|
user1 = User.objects.create(username="user1")
|
||||||
@ -875,7 +878,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- set_storage_path only called if user can edit all docs
|
- set_storage_path only called if user can edit all docs
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "set_storage_path")
|
||||||
self.doc1.owner = User.objects.get(username="temp_admin")
|
self.doc1.owner = User.objects.get(username="temp_admin")
|
||||||
self.doc1.save()
|
self.doc1.save()
|
||||||
user1 = User.objects.create(username="user1")
|
user1 = User.objects.create(username="user1")
|
||||||
@ -919,8 +922,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.rotate")
|
@mock.patch("documents.serialisers.bulk_edit.rotate")
|
||||||
def test_rotate(self, m):
|
def test_rotate(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "rotate")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -974,8 +976,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.merge")
|
@mock.patch("documents.serialisers.bulk_edit.merge")
|
||||||
def test_merge(self, m):
|
def test_merge(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "merge")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -1003,8 +1004,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
user1 = User.objects.create(username="user1")
|
user1 = User.objects.create(username="user1")
|
||||||
self.client.force_authenticate(user=user1)
|
self.client.force_authenticate(user=user1)
|
||||||
|
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "merge")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -1053,8 +1053,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- The API fails with a correct error code
|
- The API fails with a correct error code
|
||||||
"""
|
"""
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "merge")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -1074,8 +1073,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.split")
|
@mock.patch("documents.serialisers.bulk_edit.split")
|
||||||
def test_split(self, m):
|
def test_split(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "split")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -1165,8 +1163,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.delete_pages")
|
@mock.patch("documents.serialisers.bulk_edit.delete_pages")
|
||||||
def test_delete_pages(self, m):
|
def test_delete_pages(self, m):
|
||||||
m.return_value = "OK"
|
self.setup_mock(m, "delete_pages")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/documents/bulk_edit/",
|
"/api/documents/bulk_edit/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@ -1254,3 +1251,87 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertIn(b"pages must be a list of integers", response.content)
|
self.assertIn(b"pages must be a list of integers", response.content)
|
||||||
|
|
||||||
|
@override_settings(AUDIT_LOG_ENABLED=True)
|
||||||
|
def test_bulk_edit_audit_log_enabled_simple_field(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Audit log is enabled
|
||||||
|
WHEN:
|
||||||
|
- API to bulk edit documents is called
|
||||||
|
THEN:
|
||||||
|
- Audit log is created
|
||||||
|
"""
|
||||||
|
LogEntry.objects.all().delete()
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/documents/bulk_edit/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"documents": [self.doc1.id],
|
||||||
|
"method": "set_correspondent",
|
||||||
|
"parameters": {"correspondent": self.c2.id},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(LogEntry.objects.filter(object_pk=self.doc1.id).count(), 1)
|
||||||
|
|
||||||
|
@override_settings(AUDIT_LOG_ENABLED=True)
|
||||||
|
def test_bulk_edit_audit_log_enabled_tags(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Audit log is enabled
|
||||||
|
WHEN:
|
||||||
|
- API to bulk edit tags is called
|
||||||
|
THEN:
|
||||||
|
- Audit log is created
|
||||||
|
"""
|
||||||
|
LogEntry.objects.all().delete()
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/documents/bulk_edit/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"documents": [self.doc1.id],
|
||||||
|
"method": "modify_tags",
|
||||||
|
"parameters": {
|
||||||
|
"add_tags": [self.t1.id],
|
||||||
|
"remove_tags": [self.t2.id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(LogEntry.objects.filter(object_pk=self.doc1.id).count(), 1)
|
||||||
|
|
||||||
|
@override_settings(AUDIT_LOG_ENABLED=True)
|
||||||
|
def test_bulk_edit_audit_log_enabled_custom_fields(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Audit log is enabled
|
||||||
|
WHEN:
|
||||||
|
- API to bulk edit custom fields is called
|
||||||
|
THEN:
|
||||||
|
- Audit log is created
|
||||||
|
"""
|
||||||
|
LogEntry.objects.all().delete()
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/documents/bulk_edit/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"documents": [self.doc1.id],
|
||||||
|
"method": "modify_custom_fields",
|
||||||
|
"parameters": {
|
||||||
|
"add_custom_fields": [self.cf1.id],
|
||||||
|
"remove_custom_fields": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(LogEntry.objects.filter(object_pk=self.doc1.id).count(), 2)
|
||||||
|
@ -6,12 +6,14 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test import override_settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.tests.utils import DirectoriesMixin
|
from documents.tests.utils import DirectoriesMixin
|
||||||
|
from paperless.models import ApplicationConfiguration
|
||||||
|
|
||||||
|
|
||||||
class TestViews(DirectoriesMixin, TestCase):
|
class TestViews(DirectoriesMixin, TestCase):
|
||||||
@ -67,6 +69,26 @@ class TestViews(DirectoriesMixin, TestCase):
|
|||||||
f"frontend/{language_actual}/main.js",
|
f"frontend/{language_actual}/main.js",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(BASE_URL="/paperless/")
|
||||||
|
def test_index_app_logo_with_base_url(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Existing config with app_logo specified
|
||||||
|
WHEN:
|
||||||
|
- Index page is loaded
|
||||||
|
THEN:
|
||||||
|
- app_logo is prefixed with BASE_URL
|
||||||
|
"""
|
||||||
|
config = ApplicationConfiguration.objects.first()
|
||||||
|
config.app_logo = "/logo/example.jpg"
|
||||||
|
config.save()
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
response = self.client.get("/")
|
||||||
|
self.assertEqual(
|
||||||
|
response.context["APP_LOGO"],
|
||||||
|
f"/paperless{config.app_logo}",
|
||||||
|
)
|
||||||
|
|
||||||
def test_share_link_views(self):
|
def test_share_link_views(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
@ -26,11 +26,13 @@ from django.db.models import Case
|
|||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.db.models import IntegerField
|
from django.db.models import IntegerField
|
||||||
from django.db.models import Max
|
from django.db.models import Max
|
||||||
|
from django.db.models import Model
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.db.models import When
|
from django.db.models import When
|
||||||
from django.db.models.functions import Length
|
from django.db.models.functions import Length
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
|
from django.db.models.manager import Manager
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
@ -426,7 +428,7 @@ class DocumentViewSet(
|
|||||||
)
|
)
|
||||||
|
|
||||||
def file_response(self, pk, request, disposition):
|
def file_response(self, pk, request, disposition):
|
||||||
doc = Document.objects.select_related("owner").get(id=pk)
|
doc = Document.global_objects.select_related("owner").get(id=pk)
|
||||||
if request.user is not None and not has_perms_owner_aware(
|
if request.user is not None and not has_perms_owner_aware(
|
||||||
request.user,
|
request.user,
|
||||||
"view_document",
|
"view_document",
|
||||||
@ -961,6 +963,22 @@ class SavedViewViewSet(ModelViewSet, PassUserMixin):
|
|||||||
|
|
||||||
|
|
||||||
class BulkEditView(PassUserMixin):
|
class BulkEditView(PassUserMixin):
|
||||||
|
MODIFIED_FIELD_BY_METHOD = {
|
||||||
|
"set_correspondent": "correspondent",
|
||||||
|
"set_document_type": "document_type",
|
||||||
|
"set_storage_path": "storage_path",
|
||||||
|
"add_tag": "tags",
|
||||||
|
"remove_tag": "tags",
|
||||||
|
"modify_tags": "tags",
|
||||||
|
"modify_custom_fields": "custom_fields",
|
||||||
|
"set_permissions": None,
|
||||||
|
"delete": "deleted_at",
|
||||||
|
"rotate": "checksum",
|
||||||
|
"delete_pages": "checksum",
|
||||||
|
"split": None,
|
||||||
|
"merge": None,
|
||||||
|
}
|
||||||
|
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
serializer_class = BulkEditSerializer
|
serializer_class = BulkEditSerializer
|
||||||
parser_classes = (parsers.JSONParser,)
|
parser_classes = (parsers.JSONParser,)
|
||||||
@ -1013,8 +1031,53 @@ class BulkEditView(PassUserMixin):
|
|||||||
return HttpResponseForbidden("Insufficient permissions")
|
return HttpResponseForbidden("Insufficient permissions")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
modified_field = self.MODIFIED_FIELD_BY_METHOD[method.__name__]
|
||||||
|
if settings.AUDIT_LOG_ENABLED and modified_field:
|
||||||
|
old_documents = {
|
||||||
|
obj["pk"]: obj
|
||||||
|
for obj in Document.objects.filter(pk__in=documents).values(
|
||||||
|
"pk",
|
||||||
|
"correspondent",
|
||||||
|
"document_type",
|
||||||
|
"storage_path",
|
||||||
|
"tags",
|
||||||
|
"custom_fields",
|
||||||
|
"deleted_at",
|
||||||
|
"checksum",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
# TODO: parameter validation
|
# TODO: parameter validation
|
||||||
result = method(documents, **parameters)
|
result = method(documents, **parameters)
|
||||||
|
|
||||||
|
if settings.AUDIT_LOG_ENABLED and modified_field:
|
||||||
|
new_documents = Document.objects.filter(pk__in=documents)
|
||||||
|
for doc in new_documents:
|
||||||
|
old_value = old_documents[doc.pk][modified_field]
|
||||||
|
new_value = getattr(doc, modified_field)
|
||||||
|
|
||||||
|
if isinstance(new_value, Model):
|
||||||
|
# correspondent, document type, etc.
|
||||||
|
new_value = new_value.pk
|
||||||
|
elif isinstance(new_value, Manager):
|
||||||
|
# tags, custom fields
|
||||||
|
new_value = list(new_value.values_list("pk", flat=True))
|
||||||
|
|
||||||
|
LogEntry.objects.log_create(
|
||||||
|
instance=doc,
|
||||||
|
changes={
|
||||||
|
modified_field: [
|
||||||
|
old_value,
|
||||||
|
new_value,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
action=LogEntry.Action.UPDATE,
|
||||||
|
actor=user,
|
||||||
|
additional_data={
|
||||||
|
"reason": f"Bulk edit: {method.__name__}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return Response({"result": result})
|
return Response({"result": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"An error occurred performing bulk edit: {e!s}")
|
logger.warning(f"An error occurred performing bulk edit: {e!s}")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user