mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-03 18:54:40 -05:00
Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
170ddbc76f | ||
![]() |
91b364f531 | ||
![]() |
463696e6a8 | ||
![]() |
cc2b836646 | ||
![]() |
816a4c1887 | ||
![]() |
604da64f52 | ||
![]() |
247edc89f0 | ||
![]() |
01c11f9fa0 | ||
![]() |
9434851c54 | ||
![]() |
0fa9d71da8 | ||
![]() |
3872c3c49b | ||
![]() |
018b53db1b | ||
![]() |
ba853be00d | ||
![]() |
a720ed6a77 | ||
![]() |
3d3300ac32 | ||
![]() |
df87599f1b | ||
![]() |
367b5e73cb | ||
![]() |
d6cbea97f9 | ||
![]() |
e43ab23a45 | ||
![]() |
5b75321a31 | ||
![]() |
2f2b5b90ea | ||
![]() |
cf3dedf4bb | ||
![]() |
d874145f52 | ||
![]() |
800f4deb90 | ||
![]() |
c22af0a782 | ||
![]() |
8465b8cfca | ||
![]() |
6ae65e0ab4 | ||
![]() |
6a64bf6fc1 | ||
![]() |
f038bb90bb | ||
![]() |
a5ba14e446 | ||
![]() |
70f657beff | ||
![]() |
b1085c36ff | ||
![]() |
1896aa7da1 | ||
![]() |
087856c535 | ||
![]() |
27f1364caa | ||
![]() |
bec5365ac9 | ||
![]() |
0db14d6f74 | ||
![]() |
ccb44e1bd1 | ||
![]() |
8c3db627e4 | ||
![]() |
56204f8497 | ||
![]() |
fc75e88cb7 | ||
![]() |
d2719b5309 | ||
![]() |
e5254abfcd | ||
![]() |
96b8d35a00 | ||
![]() |
035b0a449b | ||
![]() |
6b20177b09 | ||
![]() |
15861ea41a | ||
![]() |
d836756e19 | ||
![]() |
191d9a7b2b | ||
![]() |
0394cd7631 | ||
![]() |
c1ddff4ee5 | ||
![]() |
55cc428cd3 | ||
![]() |
39505c9764 | ||
![]() |
887a2f9be1 | ||
![]() |
1328fa9e5e | ||
![]() |
513830f319 | ||
![]() |
440abcda7c | ||
![]() |
6f3fdbecea | ||
![]() |
3a46cc33ee | ||
![]() |
ae3eb84e01 | ||
![]() |
7a73e18596 | ||
![]() |
c89779e8fc | ||
![]() |
5ca8d10d04 | ||
![]() |
1ce6aef57c | ||
![]() |
49b23aafa3 | ||
![]() |
f888647b12 | ||
![]() |
065ff6eaf5 | ||
![]() |
bf976fe188 | ||
![]() |
5ce73574c8 | ||
![]() |
5d94a983d2 | ||
![]() |
25366af4bb | ||
![]() |
f397c5472c | ||
![]() |
6273eb0061 | ||
![]() |
734fd7c0fc | ||
![]() |
8797a58efc | ||
![]() |
8da85d3609 | ||
![]() |
127d30918d | ||
![]() |
3b553f6455 | ||
![]() |
6d934da5dd | ||
![]() |
aa3d91a338 | ||
![]() |
d64818b46c | ||
![]() |
99a18516b2 | ||
![]() |
30b0a30146 | ||
![]() |
cb10617979 | ||
![]() |
265432f2a5 | ||
![]() |
a13e9f23b1 | ||
![]() |
65b37f61ca | ||
![]() |
7751755399 | ||
![]() |
14e2ad7bc4 | ||
![]() |
dfc23a2b38 | ||
![]() |
d2fc840293 | ||
![]() |
37fe6fb9c3 | ||
![]() |
a21ec76997 | ||
![]() |
501d8d9683 | ||
![]() |
8562ca9a77 | ||
![]() |
29641e5d66 | ||
![]() |
ee7308be2d | ||
![]() |
ef4009e94f | ||
![]() |
27d2ae6976 | ||
![]() |
0f9675f9d6 | ||
![]() |
bac4a63cc8 | ||
![]() |
0453787d38 | ||
![]() |
afc3e41f13 | ||
![]() |
86d6316cc9 | ||
![]() |
7b2c1f82f5 | ||
![]() |
e2a932d744 | ||
![]() |
b978994525 | ||
![]() |
6da237dd9e | ||
![]() |
50c1978d36 | ||
![]() |
fdb310c497 | ||
![]() |
ce121a261d | ||
![]() |
ebdfd4241a | ||
![]() |
9cbb1c5726 | ||
![]() |
85dabccbe7 | ||
![]() |
a9a8189d4b | ||
![]() |
30579112d2 | ||
![]() |
ccfd009c1a | ||
![]() |
044a939623 | ||
![]() |
203bc162cd | ||
![]() |
31f03ef1d3 | ||
![]() |
4d3552dc64 | ||
![]() |
ea8a52404f | ||
![]() |
0ae9aecdef | ||
![]() |
4de4789605 | ||
![]() |
950bb46827 | ||
![]() |
44936dc5f0 | ||
![]() |
1140a878b4 | ||
![]() |
efb49af7ac | ||
![]() |
b5a8106a6a | ||
![]() |
0f80eee54e | ||
![]() |
0e237fa459 |
1
.github/workflows/ansible.yml
vendored
1
.github/workflows/ansible.yml
vendored
@@ -47,7 +47,6 @@ jobs:
|
||||
molecule converge
|
||||
molecule idempotence
|
||||
molecule verify
|
||||
molecule destroy
|
||||
working-directory: "${{ github.repository }}"
|
||||
# # https://galaxy.ansible.com/docs/contributing/importing.html
|
||||
# release:
|
||||
|
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.7
|
||||
-
|
||||
name: Get pip cache dir
|
||||
id: pip-cache
|
||||
@@ -35,8 +35,6 @@ jobs:
|
||||
-
|
||||
name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends libpoppler-cpp-dev
|
||||
pip install --upgrade pipenv
|
||||
pipenv install --system --dev --ignore-pipfile
|
||||
-
|
||||
@@ -51,6 +49,39 @@ jobs:
|
||||
name: documentation
|
||||
path: docs/_build/html/
|
||||
|
||||
codestyle:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
-
|
||||
name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
-
|
||||
name: Persistent Github pip cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip${{ matrix.python-version }}
|
||||
-
|
||||
name: Install dependencies
|
||||
run: |
|
||||
pip install --upgrade pipenv
|
||||
pipenv install --system --dev --ignore-pipfile
|
||||
-
|
||||
name: Codestyle
|
||||
run: |
|
||||
cd src/
|
||||
pycodestyle
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
@@ -78,10 +109,10 @@ jobs:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip${{ matrix.python-version }}
|
||||
-
|
||||
name: Prepare tests
|
||||
name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends libpoppler-cpp-dev unpaper tesseract-ocr imagemagick ghostscript optipng
|
||||
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript optipng
|
||||
pip install --upgrade pipenv
|
||||
pipenv install --system --dev --ignore-pipfile
|
||||
-
|
||||
@@ -89,11 +120,6 @@ jobs:
|
||||
run: |
|
||||
cd src/
|
||||
pytest
|
||||
-
|
||||
name: Codestyle
|
||||
run: |
|
||||
cd src/
|
||||
pycodestyle
|
||||
-
|
||||
name: Publish coverage results
|
||||
if: matrix.python-version == '3.8'
|
||||
@@ -114,6 +140,13 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '15'
|
||||
-
|
||||
name: Configure version on dev branches
|
||||
if: startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||
git_branch=${GITHUB_REF#refs/heads/}
|
||||
sed -i -E "s/version: \"(.*)\"/version: \"${git_branch} ${git_hash}\"/g" src-ui/src/environments/environment.prod.ts
|
||||
-
|
||||
name: Build frontend
|
||||
run: ./compile-frontend.sh
|
||||
@@ -125,7 +158,7 @@ jobs:
|
||||
path: src/documents/static/frontend/
|
||||
|
||||
build-release:
|
||||
needs: [frontend, documentation, tests]
|
||||
needs: [frontend, documentation, tests, codestyle]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
@@ -140,7 +173,7 @@ jobs:
|
||||
name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends libpoppler-cpp-dev gettext liblept5
|
||||
sudo apt-get install -qq --no-install-recommends gettext liblept5
|
||||
pip3 install -r requirements.txt
|
||||
-
|
||||
name: Download frontend artifact
|
||||
@@ -235,7 +268,7 @@ jobs:
|
||||
build-docker-image:
|
||||
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/ng-'))
|
||||
runs-on: ubuntu-latest
|
||||
needs: [frontend, tests]
|
||||
needs: [frontend, tests, codestyle]
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
|
@@ -9,11 +9,11 @@ If you want to implement something big: Please start a discussion about that in
|
||||
|
||||
## Python
|
||||
|
||||
Use python 3.6 for development. Paperless supports python 3.6, 3.7 and 3.8.
|
||||
Paperless supports python 3.6, 3.7, 3.8 and 3.9.
|
||||
|
||||
## Branches
|
||||
|
||||
master always reflects the latest release.
|
||||
master always reflects the latest release. Apart from changes to the documentation or readme, absolutely no functional changes on this branch in between releases.
|
||||
|
||||
dev contains all changes that will be part of the next release. Use this branch to start making your changes.
|
||||
|
||||
|
@@ -67,7 +67,6 @@ COPY requirements.txt ../
|
||||
RUN apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
build-essential \
|
||||
libpoppler-cpp-dev \
|
||||
libpq-dev \
|
||||
libqpdf-dev \
|
||||
&& python3 -m pip install --upgrade --no-cache-dir supervisor \
|
||||
|
2
Pipfile
2
Pipfile
@@ -23,7 +23,6 @@ imap-tools = "*"
|
||||
langdetect = "*"
|
||||
# numpy 1.20.0 drops python 3.6 support
|
||||
numpy = "~=1.19.5"
|
||||
pdftotext = "*"
|
||||
pathvalidate = "*"
|
||||
# pinned to 8.1.0, since aarch64 wheels might not be available beyond that https://github.com/python-pillow/Pillow/issues/5202
|
||||
pillow = "==8.1.0"
|
||||
@@ -55,6 +54,7 @@ concurrent-log-handler = "*"
|
||||
uvloop = "~=0.14.0"
|
||||
# TODO: keep an eye on piwheel builds and update this once available (https://www.piwheels.org/project/cryptography/)
|
||||
cryptography = "~=3.3.2"
|
||||
"pdfminer.six" = "*"
|
||||
|
||||
[dev-packages]
|
||||
coveralls = "*"
|
||||
|
48
Pipfile.lock
generated
48
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "bd8b69979d91f4d8c52cac127c891d750c52959807220a98dcf74fed126bfa26"
|
||||
"sha256": "71959eb287fc97969263be5e3a1b1f4f369b7a5ace85bd1947a25b9b92e17e8a"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -227,11 +227,11 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f",
|
||||
"sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"
|
||||
"sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7",
|
||||
"sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.6"
|
||||
"version": "==3.1.7"
|
||||
},
|
||||
"django-cors-headers": {
|
||||
"hashes": [
|
||||
@@ -610,15 +610,8 @@
|
||||
"sha256:b9aac0ebeafb21c08bf65f2039f4b2c5f78a3449d0a41df711d72445649e952a",
|
||||
"sha256:d78877ba8d8bf957f3bb636c4f73f4f6f30f56c461993877ac22c39c20837509"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==20201018"
|
||||
},
|
||||
"pdftotext": {
|
||||
"hashes": [
|
||||
"sha256:98aeb8b07a4127e1a30223bd933ef080bbd29aa88f801717ca6c5618380b8aa6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.5"
|
||||
"version": "==20201018"
|
||||
},
|
||||
"pikepdf": {
|
||||
"hashes": [
|
||||
@@ -1104,11 +1097,11 @@
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:11d544652edbdfc9cc41aa4c8a5c166513e279f3f2d9f1a9e1c89935b51de6ff",
|
||||
"sha256:a89be573bfddb81bb0b395a416d5e55e3ecc73ce95a368a4f6360bedea33195e"
|
||||
"sha256:65185676e9fdf20d154cffd1c5de8e39ef9696ff7e59fe0156b1b08e468736af",
|
||||
"sha256:70657337ec104eb4f3fb229285358f23f045433f6aea26846cdd55f0fd68945c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.56.2"
|
||||
"version": "==4.57.0"
|
||||
},
|
||||
"twisted": {
|
||||
"extras": [
|
||||
@@ -1146,11 +1139,11 @@
|
||||
},
|
||||
"txaio": {
|
||||
"hashes": [
|
||||
"sha256:1488d31d564a116538cc1265ac3f7979fb6223bb5a9e9f1479436ee2c17d8549",
|
||||
"sha256:a8676d6c68aea1f0e2548c4afdb8e6253873af3bc2659bb5bcd9f39dff7ff90f"
|
||||
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
|
||||
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==20.12.1"
|
||||
"version": "==21.2.1"
|
||||
},
|
||||
"tzlocal": {
|
||||
"hashes": [
|
||||
@@ -1172,11 +1165,11 @@
|
||||
"standard"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c",
|
||||
"sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355"
|
||||
"sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202",
|
||||
"sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13.3"
|
||||
"version": "==0.13.4"
|
||||
},
|
||||
"uvloop": {
|
||||
"hashes": [
|
||||
@@ -1220,11 +1213,10 @@
|
||||
},
|
||||
"watchgod": {
|
||||
"hashes": [
|
||||
"sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a",
|
||||
"sha256:5fb60afa9558b79736395db1cb60ad3ed59df5c2f507a3ff729220cf1251ffdc",
|
||||
"sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858"
|
||||
"sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29",
|
||||
"sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"
|
||||
],
|
||||
"version": "==0.6"
|
||||
"version": "==0.7"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
@@ -1498,11 +1490,11 @@
|
||||
},
|
||||
"faker": {
|
||||
"hashes": [
|
||||
"sha256:bf2a9b3f8d00a5dada61fc4a3f80fe0d6795c7f02a138a7d2ef2db5817c7d017",
|
||||
"sha256:d4aecdb877519d06c2fdc01ffc5ecf70658981acf5e13cd07ded9892994ef5c6"
|
||||
"sha256:31a58ec5a8f4672f24da3b5ddea02c82a712de1de3179b432948e5c34d787aca",
|
||||
"sha256:aadfe0efe11ecbbbc5b3b0b0fab050c2acbd2d8e5201769546d43d236bfff663"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==6.1.1"
|
||||
"version": "==6.4.1"
|
||||
},
|
||||
"filelock": {
|
||||
"hashes": [
|
||||
|
30
README.md
30
README.md
@@ -7,9 +7,16 @@
|
||||
|
||||
# Paperless-ng
|
||||
|
||||
[Paperless](https://github.com/the-paperless-project/paperless) is an application by Daniel Quinn and contributors that indexes your scanned documents and allows you to easily search for documents and store metadata alongside your documents.
|
||||
[Paperless (click me)](https://github.com/the-paperless-project/paperless) is an application by Daniel Quinn and contributors that indexes your scanned documents and allows you to easily search for documents and store metadata alongside your documents.
|
||||
|
||||
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation.
|
||||
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. These key points should help you decide whether Paperless-ng is something you would prefer over Paperless:
|
||||
|
||||
* Interface: The new front end is the main interface for paperless-ng, the old interface still exists but most customizations (such as thumbnails for the document list) have been removed.
|
||||
* Encryption: Paperless-ng does not support GnuPG anymore, since storing your data on encrypted file systems (that you optionally mount on demand) achieves about the same result.
|
||||
* Resource usage: Paperless-ng does use a bit more resources than Paperless. Running the web server requires about 300MB of RAM or more, depending on the configuration. While adding documents, it requires about 300MB additional RAM, depending on the document. It still runs on Pi (many users do that), but it has been generally geared to better use the resources of more powerful systems.
|
||||
* API changes: If you rely on the REST API of paperless, some of its functionality has been changed.
|
||||
|
||||
For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation.
|
||||
|
||||
# How it Works
|
||||
|
||||
@@ -32,8 +39,8 @@ Here's what you get:
|
||||
* Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents.
|
||||
* Supports PDF documents, images, plain text files, and Office documents (Word, Excel, Powerpoint, and LibreOffice equivalents).
|
||||
* Office document support is optional and provided by Apache Tika (see [configuration](https://paperless-ng.readthedocs.io/en/latest/configuration.html#tika-settings))
|
||||
* Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and can be configured freely.
|
||||
* Single page application front end. Should be pretty snappy. Will be mobile friendly in the future.
|
||||
* Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely.
|
||||
* Single page application front end.
|
||||
* Includes a dashboard that shows basic statistics and has document upload.
|
||||
* Filtering by tags, correspondents, types, and more.
|
||||
* Customizable views can be saved and displayed on the dashboard.
|
||||
@@ -44,14 +51,13 @@ Here's what you get:
|
||||
* Searching for similar documents ("More like this")
|
||||
* Email processing: Paperless adds documents from your email accounts.
|
||||
* Configure multiple accounts and filters for each account.
|
||||
* When adding documents from mails, paperless can move these mails to a new folder, mark them as read, flag them or delete them.
|
||||
* When adding documents from mail, paperless can move these mail to a new folder, mark them as read, flag them as important or delete them.
|
||||
* Machine learning powered document matching.
|
||||
* Paperless learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless.
|
||||
* A task processor that processes documents in parallel and also tells you when something goes wrong. On modern multi core systems, consumption is blazing fast.
|
||||
* Optimized for multi core systems: Paperless-ng consumes multiple documents in parallel.
|
||||
* The integrated sanity checker makes sure that your document archive is in good health.
|
||||
|
||||
If you want to see some screenshots of paperless-ng in action, [some are available in the documentation](https://paperless-ng.readthedocs.io/en/latest/screenshots.html). However, some parts of the UI have changed since I took these.
|
||||
|
||||
For a complete list of changes from paperless, check out the [changelog](https://paperless-ng.readthedocs.io/en/latest/changelog.html)
|
||||
If you want to see some screenshots of paperless-ng in action, [some are available in the documentation](https://paperless-ng.readthedocs.io/en/latest/screenshots.html).
|
||||
|
||||
# Getting started
|
||||
|
||||
@@ -71,9 +77,9 @@ The documentation for Paperless-ng is available on [ReadTheDocs](https://paperle
|
||||
|
||||
# Translation
|
||||
|
||||
Paperless is currently available in English, German, Dutch and French. Translation is coordinated at transifex: https://www.transifex.com/paperless/paperless-ng
|
||||
Paperless is currently available in English, German, Dutch, French, and Portuguese.
|
||||
|
||||
If you want to see paperless in your own language, request that language at transifex and you can start translating after I approve the language.
|
||||
There's an active translation project at transifex! If you want to help out by translating paperless into your language, please head over to https://github.com/jonaswinkler/paperless-ng/issues/212 for details.
|
||||
|
||||
# Feature Requests
|
||||
|
||||
@@ -103,4 +109,4 @@ These projects also exist, but their status and compatibility with paperless-ng
|
||||
|
||||
# Important Note
|
||||
|
||||
Document scanners are typically used to scan sensitive documents. Things like your social insurance number, tax records, invoices, etc. Everything is stored in the clear without encryption by default (it needs to be searchable, so if someone has ideas on how to do that on encrypted data, I'm all ears). This means that Paperless should never be run on an untrusted host. Instead, I recommend that if you do want to use it, run it locally on a server in your own home.
|
||||
Document scanners are typically used to scan sensitive documents. Things like your social insurance number, tax records, invoices, etc. Everything is stored in the clear without encryption. This means that Paperless should never be run on an untrusted host. Instead, I recommend that if you do want to use it, run it locally on a server in your own home.
|
||||
|
@@ -18,8 +18,9 @@ paperlessng_directory: /opt/paperless-ng
|
||||
paperlessng_consumption_dir: "{{ paperlessng_directory }}/consumption"
|
||||
paperlessng_data_dir: "{{ paperlessng_directory }}/data"
|
||||
paperlessng_media_root: "{{ paperlessng_directory }}/media"
|
||||
paperlessng_static_dir: "{{ paperlessng_directory }}/static"
|
||||
paperlessng_staticdir: "{{ paperlessng_directory }}/static"
|
||||
paperlessng_filename_format:
|
||||
paperlessng_logging_dir: "{{ paperlessng_data_dir }}/log"
|
||||
paperlessng_virtualenv: "{{ paperlessng_directory }}/.venv"
|
||||
|
||||
# Hosting & Security
|
||||
@@ -36,12 +37,15 @@ paperlessng_enable_http_remote_user: False
|
||||
paperlessng_ocr_languages:
|
||||
- eng
|
||||
paperlessng_ocr_mode: skip
|
||||
paperlessng_ocr_clean: clean
|
||||
paperlessng_ocr_deskew: True
|
||||
paperlessng_ocr_rotate_pages: True
|
||||
paperlessng_ocr_rotate_pages_threshold: 12
|
||||
paperlessng_ocr_output_type: pdfa
|
||||
paperlessng_ocr_pages: 0
|
||||
paperlessng_ocr_image_dpi:
|
||||
# see https://ocrmypdf.readthedocs.io/en/latest/api.html#ocrmypdf.ocr
|
||||
paperlessng_ocr_user_args:
|
||||
#- "deskew": True # https://github.com/jonaswinkler/paperless-ng/issues/231
|
||||
- "optimize": 1
|
||||
paperlessng_use_jbig2enc: True
|
||||
paperlessng_big2enc_lossy: False
|
||||
@@ -57,10 +61,11 @@ paperlessng_consumer_polling: 0
|
||||
paperlessng_consumer_delete_duplicates: False
|
||||
paperlessng_consumer_recursive: False
|
||||
paperlessng_consumer_subdirs_as_tags: False
|
||||
paperlessng_convert_memory_limit: 0
|
||||
paperlessng_convert_tmpdir:
|
||||
paperlessng_optimize_thumbnails: True
|
||||
paperlessng_post_consume_script:
|
||||
paperlessng_filename_date_order:
|
||||
paperlessng_filename_parse_transforms:
|
||||
paperlessng_thumbnail_font_name: /usr/share/fonts/liberation/LiberationSerif-Regular.ttf
|
||||
paperlessng_ignore_dates: ""
|
||||
|
||||
|
@@ -2,9 +2,15 @@
|
||||
- name: update previous release to newest release
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: install git dependency
|
||||
apt:
|
||||
pkg: git
|
||||
- name: obtain latest git hash in current tree
|
||||
command: git rev-parse HEAD
|
||||
register: git_hash
|
||||
- name: set current github commit as version when available
|
||||
set_fact:
|
||||
paperlessng_version: "{{ lookup('env', 'GITHUB_SHA') | default('master', True) }}"
|
||||
paperlessng_version: "{{ git_hash.stdout }}"
|
||||
- name: update to newest paperless-ng release
|
||||
include_role:
|
||||
name: ansible
|
||||
|
@@ -15,7 +15,6 @@
|
||||
- imagemagick
|
||||
- optipng
|
||||
- gnupg
|
||||
- libpoppler-cpp-dev
|
||||
- libpq-dev
|
||||
- libmagic-dev
|
||||
- mime-support
|
||||
@@ -44,7 +43,7 @@
|
||||
|
||||
- name: install ocr languages
|
||||
apt:
|
||||
pkg: "{{ paperlessng_ocr_languages | map('regex_replace', '^(.*)$', 'tesseract-ocr-\\1') | list }}"
|
||||
pkg: "{{ paperlessng_ocr_languages | map('regex_replace', '^(.*)$', 'tesseract-ocr-\\1') | map('replace', '_', '-') | list }}"
|
||||
|
||||
- name: set up notesalexp repository key (for jbig2enc)
|
||||
apt_key:
|
||||
@@ -257,7 +256,7 @@
|
||||
- "{{ paperlessng_consumption_dir }}"
|
||||
- "{{ paperlessng_data_dir }}"
|
||||
- "{{ paperlessng_media_root }}"
|
||||
- "{{ paperlessng_static_dir }}"
|
||||
- "{{ paperlessng_staticdir }}"
|
||||
|
||||
- name: rename initial config
|
||||
command:
|
||||
@@ -281,9 +280,11 @@
|
||||
- regexp: PAPERLESS_MEDIA_ROOT
|
||||
line: "PAPERLESS_MEDIA_ROOT={{ paperlessng_media_root }}"
|
||||
- regexp: PAPERLESS_STATICDIR
|
||||
line: "PAPERLESS_STATICDIR={{ paperlessng_static_dir }}"
|
||||
line: "PAPERLESS_STATICDIR={{ paperlessng_staticdir }}"
|
||||
- regexp: PAPERLESS_FILENAME_FORMAT
|
||||
line: "PAPERLESS_FILENAME_FORMAT={{ paperlessng_filename_format }}"
|
||||
- regexp: PAPERLESS_LOGGING_DIR
|
||||
line: "PAPERLESS_LOGGING_DIR={{ paperlessng_logging_dir }}"
|
||||
# Hosting & Security
|
||||
- regexp: PAPERLESS_SECRET_KEY
|
||||
line: "PAPERLESS_SECRET_KEY={{ paperlessng_secret_key }}"
|
||||
@@ -303,9 +304,17 @@
|
||||
line: "PAPERLESS_ENABLE_HTTP_REMOTE_USER={{ paperlessng_enable_http_remote_user }}"
|
||||
# OCR settings
|
||||
- regexp: PAPERLESS_OCR_LANGUAGE
|
||||
line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') }}"
|
||||
line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') | replace('-','_') }}"
|
||||
- regexp: PAPERLESS_OCR_MODE
|
||||
line: "PAPERLESS_OCR_MODE={{ paperlessng_ocr_mode }}"
|
||||
- regexp: PAPERLESS_OCR_CLEAN
|
||||
line: "PAPERLESS_OCR_CLEAN={{ paperlessng_ocr_clean }}"
|
||||
- regexp: PAPERLESS_OCR_DESKEW
|
||||
line: "PAPERLESS_OCR_DESKEW={{ paperlessng_ocr_deskew }}"
|
||||
- regexp: PAPERLESS_OCR_ROTATE_PAGES
|
||||
line: "PAPERLESS_OCR_ROTATE_PAGES={{ paperlessng_ocr_rotate_pages }}"
|
||||
- regexp: PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD
|
||||
line: "PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD={{ paperlessng_ocr_rotate_pages_threshold }}"
|
||||
- regexp: PAPERLESS_OCR_OUTPUT_TYPE
|
||||
line: "PAPERLESS_OCR_OUTPUT_TYPE={{ paperlessng_ocr_output_type }}"
|
||||
- regexp: PAPERLESS_OCR_PAGES
|
||||
@@ -332,6 +341,10 @@
|
||||
line: "PAPERLESS_CONSUMER_RECURSIVE={{ paperlessng_consumer_recursive }}"
|
||||
- regexp: PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS
|
||||
line: "PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS={{ paperlessng_consumer_subdirs_as_tags }}"
|
||||
- regexp: PAPERLESS_CONVERT_MEMORY_LIMIT
|
||||
line: "PAPERLESS_CONVERT_MEMORY_LIMIT={{ paperlessng_convert_memory_limit }}"
|
||||
- regexp: PAPERLESS_CONVERT_TMPDIR
|
||||
line: "PAPERLESS_CONVERT_TMPDIR={{ paperlessng_convert_tmpdir }}"
|
||||
- regexp: PAPERLESS_OPTIMIZE_THUMBNAILS
|
||||
line: "PAPERLESS_OPTIMIZE_THUMBNAILS={{ paperlessng_optimize_thumbnails }}"
|
||||
- regexp: PAPERLESS_POST_CONSUME_SCRIPT
|
||||
|
@@ -5,12 +5,42 @@
|
||||
Changelog
|
||||
*********
|
||||
|
||||
paperless-ng 1.1.4
|
||||
paperless-ng 1.2.1
|
||||
##################
|
||||
|
||||
* Added English (GB) locale.
|
||||
* `Rodrigo Avelino <https://github.com/rodavelino>`_ translated Paperless into Portuguese (Brazil)!
|
||||
|
||||
* Added ISO-8601 date display option.
|
||||
* The date input fields now respect the currently selected date format.
|
||||
|
||||
* Added a fancy icon when adding paperless to the home screen on iOS devices. Thanks to `Joel Nordell <https://github.com/joelnordell>`_.
|
||||
|
||||
* When using regular expression matching, the regular expression is now validated before saving the tag/correspondent/type.
|
||||
|
||||
* Regression fix: Dates on the front end did not respect date locale settings in some cases.
|
||||
|
||||
paperless-ng 1.2.0
|
||||
##################
|
||||
|
||||
* Changes to the OCRmyPDF integration
|
||||
|
||||
* Added support for deskewing and automatic rotation of incorrectly rotated pages. This is enabled by default, see :ref:`configuration-ocr`.
|
||||
* Better support for encrypted files.
|
||||
* Better support for various other PDF files: Paperless will now attempt to force OCR with safe options when OCR fails with the configured options.
|
||||
* Added an explicit option to skip cleaning with ``unpaper``.
|
||||
|
||||
* Download multiple selected documents as a zip archive.
|
||||
|
||||
* The document list now remembers the current page.
|
||||
|
||||
* Improved responsiveness when switching between saved views and the document list.
|
||||
|
||||
* Increased the default wait time when observing files in the consumption folder
|
||||
with polling from 1 to 5 seconds. This will decrease the likelihood of paperless
|
||||
consuming partially written files.
|
||||
|
||||
* Fixed a crash of the document archiver management command when trying to process documents with unknown mime types.
|
||||
|
||||
* Paperless no longer depends on ``libpoppler-cpp-dev``.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -23,6 +53,13 @@ paperless-ng 1.1.4
|
||||
|
||||
If using docker, this does not affect you.
|
||||
|
||||
paperless-ng 1.1.4
|
||||
##################
|
||||
|
||||
* Added English (GB) locale.
|
||||
|
||||
* Added ISO-8601 date display option.
|
||||
|
||||
paperless-ng 1.1.3
|
||||
##################
|
||||
|
||||
|
@@ -202,7 +202,6 @@ Paperless uses `OCRmyPDF <https://ocrmypdf.readthedocs.io/en/latest/>`_ for
|
||||
performing OCR on documents and images. Paperless uses sensible defaults for
|
||||
most settings, but all of them can be configured to your needs.
|
||||
|
||||
|
||||
PAPERLESS_OCR_LANGUAGE=<lang>
|
||||
Customize the language that paperless will attempt to use when
|
||||
parsing documents.
|
||||
@@ -245,6 +244,54 @@ PAPERLESS_OCR_MODE=<mode>
|
||||
The default is ``skip``, which only performs OCR when necessary and always
|
||||
creates archived documents.
|
||||
|
||||
Read more about this in the `OCRmyPDF documentation <https://ocrmypdf.readthedocs.io/en/latest/advanced.html#when-ocr-is-skipped>`_.
|
||||
|
||||
PAPERLESS_OCR_CLEAN=<mode>
|
||||
Tells paperless to use ``unpaper`` to clean any input document before
|
||||
sending it to tesseract. This uses more resources, but generally results
|
||||
in better OCR results. The following modes are available:
|
||||
|
||||
* ``clean``: Apply unpaper.
|
||||
* ``clean-final``: Apply unpaper, and use the cleaned images to build the
|
||||
output file instead of the original images.
|
||||
* ``none``: Do not apply unpaper.
|
||||
|
||||
Defaults to ``clean``.
|
||||
|
||||
.. note::
|
||||
|
||||
``clean-final`` is incompatible with ocr mode ``redo``. When both
|
||||
``clean-final`` and the ocr mode ``redo`` is configured, ``clean``
|
||||
is used instead.
|
||||
|
||||
PAPERLESS_OCR_DESKEW=<bool>
|
||||
Tells paperless to correct skewing (slight rotation of input images mainly
|
||||
due to improper scanning)
|
||||
|
||||
Defaults to ``true``, which enables this feature.
|
||||
|
||||
.. note::
|
||||
|
||||
Deskewing is incompatible with ocr mode ``redo``. Deskewing will get
|
||||
disabled automatically if ``redo`` is used as the ocr mode.
|
||||
|
||||
PAPERLESS_OCR_ROTATE_PAGES=<bool>
|
||||
Tells paperless to correct page rotation (90°, 180° and 270° rotation).
|
||||
|
||||
If you notice that paperless is not rotating incorrectly rotated
|
||||
pages (or vice versa), try adjusting the threshold up or down (see below).
|
||||
|
||||
Defaults to ``true``, which enables this feature.
|
||||
|
||||
|
||||
PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD=<num>
|
||||
Adjust the threshold for automatic page rotation by ``PAPERLESS_OCR_ROTATE_PAGES``.
|
||||
This is an arbitrary value reported by tesseract. "15" is a very conservative value,
|
||||
whereas "2" is a very aggressive option and will often result in correctly rotated pages
|
||||
being rotated as well.
|
||||
|
||||
Defaults to "12".
|
||||
|
||||
PAPERLESS_OCR_OUTPUT_TYPE=<type>
|
||||
Specify the the type of PDF documents that paperless should produce.
|
||||
|
||||
@@ -271,7 +318,6 @@ PAPERLESS_OCR_PAGES=<num>
|
||||
|
||||
Defaults to 0, which disables this feature and always uses all pages.
|
||||
|
||||
|
||||
PAPERLESS_OCR_IMAGE_DPI=<num>
|
||||
Paperless will OCR any images you put into the system and convert them
|
||||
into PDF documents. This is useful if your scanner produces images.
|
||||
@@ -282,8 +328,8 @@ PAPERLESS_OCR_IMAGE_DPI=<num>
|
||||
|
||||
Set this to the DPI your scanner produces images at.
|
||||
|
||||
Default is none, which causes paperless to fail if no DPI information is
|
||||
present in an image.
|
||||
Default is none, which will automatically calculate image DPI so that
|
||||
the produced PDF documents are A4 sized.
|
||||
|
||||
|
||||
PAPERLESS_OCR_USER_ARGS=<json>
|
||||
@@ -292,7 +338,7 @@ PAPERLESS_OCR_USER_ARGS=<json>
|
||||
the API of OCRmyPDF, you have to specify these in a format that can be
|
||||
passed to the API. See `the API reference of OCRmyPDF <https://ocrmypdf.readthedocs.io/en/latest/api.html#reference>`_
|
||||
for valid parameters. All command line options are supported, but they
|
||||
use underscores instead of dashed.
|
||||
use underscores instead of dashes.
|
||||
|
||||
.. caution::
|
||||
|
||||
@@ -352,7 +398,7 @@ requires are as follows:
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
|
||||
# ...
|
||||
|
||||
gotenberg:
|
||||
@@ -582,10 +628,10 @@ USERMAP_UID=<uid>
|
||||
.. code:: shell-session
|
||||
|
||||
$ id -u
|
||||
|
||||
|
||||
Paperless will change ownership on its folders to this user, so you need to get this right
|
||||
in order to be able to write to the consumption directory.
|
||||
|
||||
|
||||
Defaults to 1000.
|
||||
|
||||
USERMAP_GID=<gid>
|
||||
@@ -595,10 +641,10 @@ USERMAP_GID=<gid>
|
||||
.. code:: shell-session
|
||||
|
||||
$ id -g
|
||||
|
||||
|
||||
Paperless will change ownership on its folders to this group, so you need to get this right
|
||||
in order to be able to write to the consumption directory.
|
||||
|
||||
|
||||
Defaults to 1000.
|
||||
|
||||
PAPERLESS_OCR_LANGUAGES=<list>
|
||||
|
@@ -25,7 +25,7 @@ This section describes the steps you need to take to start development on paperl
|
||||
|
||||
* Python 3.6.
|
||||
* All dependencies listed in the :ref:`Bare metal route <setup-bare_metal>`
|
||||
* redis. You can either install redis or use the included scritps/start-services.sh
|
||||
* redis. You can either install redis or use the included scripts/start-services.sh
|
||||
to use docker to fire up a redis instance (and some other services such as tika,
|
||||
gotenberg and a postgresql server).
|
||||
|
||||
@@ -109,6 +109,30 @@ This will build the front end and put it in a location from which the Django ser
|
||||
it as static content. This way, you can verify that authentication is working.
|
||||
|
||||
|
||||
Building the documentation
|
||||
==========================
|
||||
|
||||
The documentation is built using sphinx. I've configured ReadTheDocs to automatically build
|
||||
the documentation when changes are pushed. If you want to build the documentation locally,
|
||||
this is how you do it:
|
||||
|
||||
1. Install python dependencies.
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ cd /path/to/paperless
|
||||
$ pipenv install --dev
|
||||
|
||||
2. Build the documentation
|
||||
|
||||
.. code:: shell-session
|
||||
|
||||
$ cd /path/to/paperless/docs
|
||||
$ pipenv run make clean html
|
||||
|
||||
This will build the HTML documentation, and put the resulting files in the ``_build/html``
|
||||
directory.
|
||||
|
||||
Extending Paperless
|
||||
===================
|
||||
|
||||
|
@@ -280,7 +280,6 @@ writing. Windows is not and will never be supported.
|
||||
* ``imagemagick`` >= 6 for PDF conversion
|
||||
* ``optipng`` for optimizing thumbnails
|
||||
* ``gnupg`` for handling encrypted documents
|
||||
* ``libpoppler-cpp-dev`` for PDF to text conversion
|
||||
* ``libpq-dev`` for PostgreSQL
|
||||
* ``libmagic-dev`` for mime type detection
|
||||
* ``mime-support`` for mime type detection
|
||||
@@ -354,7 +353,7 @@ writing. Windows is not and will never be supported.
|
||||
.. code:: shell-session
|
||||
|
||||
sudo -Hu paperless pip3 install -r requirements.txt
|
||||
|
||||
|
||||
This will install all python dependencies in the home directory of
|
||||
the new paperless user.
|
||||
|
||||
@@ -775,6 +774,9 @@ configuring some options in paperless can help improve performance immensely:
|
||||
your documents before feeding them into paperless. Some scanners are able to
|
||||
do this! You might want to even specify ``skip_noarchive`` to skip archive
|
||||
file generation for already ocr'ed documents entirely.
|
||||
* If you want to perform OCR on the the device, consider using ``PAPERLESS_OCR_CLEAN=none``.
|
||||
This will speed up OCR times and use less memory at the expense of slightly worse
|
||||
OCR results.
|
||||
* Set ``PAPERLESS_OPTIMIZE_THUMBNAILS`` to 'false' if you want faster consumption
|
||||
times. Thumbnails will be about 20% larger.
|
||||
* If using docker, consider setting ``PAPERLESS_WEBSERVER_WORKERS`` to
|
||||
@@ -803,7 +805,7 @@ Using nginx as a reverse proxy
|
||||
##############################
|
||||
|
||||
If you want to expose paperless to the internet, you should hide it behind a
|
||||
reverse proxy with SSL enabled.
|
||||
reverse proxy with SSL enabled.
|
||||
|
||||
In addition to the usual configuration for SSL,
|
||||
the following configuration is required for paperless to operate:
|
||||
|
@@ -41,6 +41,10 @@
|
||||
#PAPERLESS_OCR_OUTPUT_TYPE=pdfa
|
||||
#PAPERLESS_OCR_PAGES=1
|
||||
#PAPERLESS_OCR_IMAGE_DPI=300
|
||||
#PAPERLESS_OCR_CLEAN=clean
|
||||
#PAPERLESS_OCR_DESKEW=true
|
||||
#PAPERLESS_OCR_ROTATE_PAGES=true
|
||||
#PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD=12.0
|
||||
#PAPERLESS_OCR_USER_ARGS={}
|
||||
#PAPERLESS_CONVERT_MEMORY_LIMIT=0
|
||||
#PAPERLESS_CONVERT_TMPDIR=/var/tmp/paperless
|
||||
|
@@ -32,7 +32,7 @@ django-extensions==3.1.1
|
||||
django-filter==2.4.0
|
||||
django-picklefield==3.0.1; python_version >= '3'
|
||||
django-q==1.3.4
|
||||
django==3.1.6
|
||||
django==3.1.7
|
||||
djangorestframework==3.12.2
|
||||
filelock==3.0.12
|
||||
fuzzywuzzy[speedup]==0.18.0
|
||||
@@ -55,8 +55,7 @@ msgpack==1.0.2
|
||||
numpy==1.19.5
|
||||
ocrmypdf==11.6.2
|
||||
pathvalidate==2.3.2
|
||||
pdfminer.six==20201018; python_version >= '3.4'
|
||||
pdftotext==2.1.5
|
||||
pdfminer.six==20201018
|
||||
pikepdf==2.5.2
|
||||
pillow==8.1.0
|
||||
pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
@@ -86,15 +85,15 @@ sortedcontainers==2.3.0
|
||||
sqlparse==0.4.1; python_version >= '3.5'
|
||||
threadpoolctl==2.1.0; python_version >= '3.5'
|
||||
tika==1.24
|
||||
tqdm==4.56.2
|
||||
tqdm==4.57.0
|
||||
twisted[tls]==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
txaio==20.12.1; python_version >= '3.6'
|
||||
txaio==21.2.1; python_version >= '3.6'
|
||||
tzlocal==2.1
|
||||
urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
|
||||
uvicorn[standard]==0.13.3
|
||||
uvicorn[standard]==0.13.4
|
||||
uvloop==0.14.0
|
||||
watchdog==1.0.2
|
||||
watchgod==0.6
|
||||
watchgod==0.7
|
||||
wcwidth==0.2.5
|
||||
websockets==8.1
|
||||
whitenoise==5.2.0
|
||||
|
16
resources/logo.txt
Normal file
16
resources/logo.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
9w
|
||||
{@@N
|
||||
Q@@@@H
|
||||
G@@@@@@@\
|
||||
SilN@@@@@@@
|
||||
*Q *@@@@@@@@S /= = = = = = = = = = = = = = = = = =\
|
||||
*@ B@@@@@@@@N || ||
|
||||
N R$ A@@@@@@@@@@ || PAPERLESS-NG ||
|
||||
x@@ $U B@@@@@@@@@R || ||
|
||||
N@@N^ @ N@@@@@@@@@* \= = = = = = = = = = = = = = = = = =/
|
||||
|@@@u @ E@@@@@@@@l
|
||||
Q@@@ \ Px@@@@@@P
|
||||
1@@S` @@@o'
|
||||
z$ ;
|
||||
v
|
||||
/
|
@@ -19,7 +19,8 @@
|
||||
"de": "src/locale/messages.de.xlf",
|
||||
"nl-NL": "src/locale/messages.nl_NL.xlf",
|
||||
"fr": "src/locale/messages.fr.xlf",
|
||||
"en-GB": "src/locale/messages.en_GB.xlf"
|
||||
"en-GB": "src/locale/messages.en_GB.xlf",
|
||||
"pt-BR": "src/locale/messages.pt_BR.xlf"
|
||||
}
|
||||
},
|
||||
"architect": {
|
||||
@@ -36,6 +37,7 @@
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/apple-touch-icon.png",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest", {
|
||||
"glob": "pdf.worker.min.js",
|
||||
@@ -112,6 +114,7 @@
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/apple-touch-icon.png",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
|
@@ -52,17 +52,17 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2155249406916744630" datatype="html">
|
||||
<source>View "<x id="PH" equiv-text="this.list.savedView.name"/>" saved successfully.</source>
|
||||
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6837554170707123455" datatype="html">
|
||||
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9ca82952a6bc860b5391d5975322d8af8ceddfa4" datatype="html">
|
||||
@@ -114,8 +114,8 @@
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="72e7d343f9165602cce1ca7faffbc565fd31ef92" datatype="html">
|
||||
<source>Save "<x id="INTERPOLATION" equiv-text="{{list.savedViewTitle}}"/>"</source>
|
||||
<trans-unit id="5f5ce787c428d917c30c9bd70789a618e09743a7" datatype="html">
|
||||
<source>Save "<x id="INTERPOLATION" equiv-text="{{list.activeSavedViewTitle}}"/>"</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
@@ -513,13 +513,6 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html">
|
||||
<source>Filter</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5610279464668232148" datatype="html">
|
||||
<source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
|
||||
<context-group purpose="location">
|
||||
@@ -538,21 +531,21 @@
|
||||
<source>Use system language</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7729897675462249787" datatype="html">
|
||||
<source>Use date format of display language</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">95</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8488620293789898901" datatype="html">
|
||||
<source>Error while storing settings on server: <x id="PH" equiv-text="JSON.stringify(error.error)"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="121cc5391cd2a5115bc2b3160379ee5b36cd7716" datatype="html">
|
||||
@@ -1081,6 +1074,13 @@
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6523384805359286307" datatype="html">
|
||||
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="02d184c288f567825a1fcbf83bcd3099a10853d5" datatype="html">
|
||||
<source>Filter tags</source>
|
||||
<context-group purpose="location">
|
||||
@@ -1219,21 +1219,21 @@
|
||||
<source>Error executing bulk operation: <x id="PH" equiv-text="JSON.stringify(error.error)"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7894972847287473517" datatype="html">
|
||||
<source>"<x id="PH" equiv-text="items[0].name"/>"</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">113</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8639884465898458690" datatype="html">
|
||||
<source>"<x id="PH" equiv-text="items[0].name"/>" and "<x id="PH_1" equiv-text="items[1].name"/>"</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">This is for messages like 'modify "tag1" and "tag2"'</note>
|
||||
</trans-unit>
|
||||
@@ -1241,7 +1241,7 @@
|
||||
<source>, </source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">this is used to separate enumerations and should probably be a comma and a whitespace in most languages</note>
|
||||
</trans-unit>
|
||||
@@ -1249,7 +1249,7 @@
|
||||
<source><x id="PH" equiv-text="list"/> and "<x id="PH_1" equiv-text="items[items.length - 1].name"/>"</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note>
|
||||
</trans-unit>
|
||||
@@ -1257,112 +1257,112 @@
|
||||
<source>Confirm tags assignment</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6619516195038467207" datatype="html">
|
||||
<source>This operation will add the tag "<x id="PH" equiv-text="tag.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1894412783609570695" datatype="html">
|
||||
<source>This operation will add the tags <x id="PH" equiv-text="this._localizeList(changedTags.itemsToAdd)"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7181166515756808573" datatype="html">
|
||||
<source>This operation will remove the tag "<x id="PH" equiv-text="tag.name"/>" from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3819792277998068944" datatype="html">
|
||||
<source>This operation will remove the tags <x id="PH" equiv-text="this._localizeList(changedTags.itemsToRemove)"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
<context context-type="linenumber">137</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2739066218579571288" datatype="html">
|
||||
<source>This operation will add the tags <x id="PH" equiv-text="this._localizeList(changedTags.itemsToAdd)"/> and remove the tags <x id="PH_1" equiv-text="this._localizeList(changedTags.itemsToRemove)"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
<context context-type="linenumber">139</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2996713129519325161" datatype="html">
|
||||
<source>Confirm correspondent assignment</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">158</context>
|
||||
<context context-type="linenumber">159</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6900893559485781849" datatype="html">
|
||||
<source>This operation will assign the correspondent "<x id="PH" equiv-text="correspondent.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">160</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1257522660364398440" datatype="html">
|
||||
<source>This operation will remove the correspondent from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5393409374423140648" datatype="html">
|
||||
<source>Confirm document type assignment</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="332180123895325027" datatype="html">
|
||||
<source>This operation will assign the document type "<x id="PH" equiv-text="documentType.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
<context context-type="linenumber">184</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2236642492594872779" datatype="html">
|
||||
<source>This operation will remove the document type from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">185</context>
|
||||
<context context-type="linenumber">186</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="749430623564850405" datatype="html">
|
||||
<source>Delete confirm</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">200</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4303174930844518780" datatype="html">
|
||||
<source>This operation will permanently delete <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5641451190833696892" datatype="html">
|
||||
<source>This operation cannot be undone.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6734339521247847366" datatype="html">
|
||||
<source>Delete document(s)</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8b0609df23817024b3bed12beb9b64fc1009f588" datatype="html">
|
||||
@@ -1386,6 +1386,13 @@
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="fc2de37422d7c4af6686842283cc2afd781b6848" datatype="html">
|
||||
<source>Download originals</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e" datatype="html">
|
||||
<source>Suggestions:</source>
|
||||
<context-group purpose="location">
|
||||
@@ -1414,20 +1421,20 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="46c8fe557cf52c9389783627d4f85453f4ddb459" datatype="html">
|
||||
<source>Documents in inbox: <x id="INTERPOLATION" equiv-text="{{statistics.documents_inbox}}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="c327c0e67bcac7494dcbaa9afb3b42d5008c6438" datatype="html">
|
||||
<source>Total documents: <x id="INTERPOLATION" equiv-text="{{statistics.documents_total}}"/></source>
|
||||
<trans-unit id="c0d907c2687c09612395aee6ef7c04ca8e5e5e0a" datatype="html">
|
||||
<source>Total documents: <x id="INTERPOLATION" equiv-text="{{statistics?.documents_total}}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="13e8d49dbcad9f9d71e66a9a56d6f328cff430c9" datatype="html">
|
||||
<source>Documents in inbox: <x id="INTERPOLATION" equiv-text="{{statistics?.documents_inbox}}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6443586946875325554" datatype="html">
|
||||
<source>Processing: <x id="PH" equiv-text="countUploadingAndProcessing"/></source>
|
||||
<context-group purpose="location">
|
||||
@@ -1591,6 +1598,13 @@
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="d6529debfc1613db22d6fa096ebfeb8a85fa739d" datatype="html">
|
||||
<source>Invalid date.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2807800733729323332" datatype="html">
|
||||
<source>Yes</source>
|
||||
<context-group purpose="location">
|
||||
@@ -1616,28 +1630,49 @@
|
||||
<source>English (US)</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6987083569809053351" datatype="html">
|
||||
<source>English (GB)</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1858110241312746425" datatype="html">
|
||||
<source>German</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3071065188816255493" datatype="html">
|
||||
<source>Dutch</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7633754075223722162" datatype="html">
|
||||
<source>French</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9184513005098760425" datatype="html">
|
||||
<source>Portuguese (Brazil)</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4912706592792948707" datatype="html">
|
||||
<source>ISO 8601</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2119857572761283468" datatype="html">
|
||||
|
11
src-ui/package-lock.json
generated
11
src-ui/package-lock.json
generated
@@ -2055,9 +2055,9 @@
|
||||
}
|
||||
},
|
||||
"@ng-bootstrap/ng-bootstrap": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-8.0.0.tgz",
|
||||
"integrity": "sha512-v77Gfd8xHH+exq0WqIqVRlxbUEHdA/2+RUJenUP2IDTQN9E1rWl7O461/kosr+0XPuxPArHQJxhh/WsCYckcNg==",
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-8.0.4.tgz",
|
||||
"integrity": "sha512-EdxTwOPOtlvfnwrglPniulmzdnXdXH3lTGaGAY1HrYRvdtGg6wicRvl+BvwVE/3Qik5NPkOWMVghUHpv3evIYg==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
@@ -5545,6 +5545,11 @@
|
||||
"schema-utils": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
|
@@ -20,9 +20,10 @@
|
||||
"@angular/platform-browser": "~10.1.5",
|
||||
"@angular/platform-browser-dynamic": "~10.1.5",
|
||||
"@angular/router": "~10.1.5",
|
||||
"@ng-bootstrap/ng-bootstrap": "^8.0.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^8.0.4",
|
||||
"@ng-select/ng-select": "^5.0.9",
|
||||
"bootstrap": "^4.5.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"ng-bootstrap": "^1.6.3",
|
||||
"ng2-pdf-viewer": "^6.3.2",
|
||||
"ngx-cookie-service": "^10.1.1",
|
||||
|
@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbDateAdapter, NgbDateParserFormatter, NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { DocumentListComponent } from './components/document-list/document-list.component';
|
||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component';
|
||||
@@ -39,7 +39,6 @@ import { SelectComponent } from './components/common/input/select/select.compone
|
||||
import { CheckComponent } from './components/common/input/check/check.component';
|
||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { DateTimeComponent } from './components/common/input/date-time/date-time.component';
|
||||
import { TagsComponent } from './components/common/input/tags/tags.component';
|
||||
import { SortableDirective } from './directives/sortable.directive';
|
||||
import { CookieService } from 'ngx-cookie-service';
|
||||
@@ -60,15 +59,21 @@ import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { NumberComponent } from './components/common/input/number/number.component';
|
||||
import { SafePipe } from './pipes/safe.pipe';
|
||||
import { CustomDatePipe } from './pipes/custom-date.pipe';
|
||||
import { DateComponent } from './components/common/input/date/date.component';
|
||||
import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter';
|
||||
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter';
|
||||
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor';
|
||||
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import localeNl from '@angular/common/locales/nl';
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import localePt from '@angular/common/locales/pt';
|
||||
import localeEnGb from '@angular/common/locales/en-GB';
|
||||
|
||||
registerLocaleData(localeFr)
|
||||
registerLocaleData(localeNl)
|
||||
registerLocaleData(localeDe)
|
||||
registerLocaleData(localePt, "pt-BR")
|
||||
registerLocaleData(localeEnGb)
|
||||
|
||||
@NgModule({
|
||||
@@ -104,7 +109,6 @@ registerLocaleData(localeEnGb)
|
||||
SelectComponent,
|
||||
CheckComponent,
|
||||
SaveViewConfigDialogComponent,
|
||||
DateTimeComponent,
|
||||
TagsComponent,
|
||||
SortableDirective,
|
||||
SavedViewWidgetComponent,
|
||||
@@ -120,7 +124,8 @@ registerLocaleData(localeEnGb)
|
||||
SelectDialogComponent,
|
||||
NumberComponent,
|
||||
SafePipe,
|
||||
CustomDatePipe
|
||||
CustomDatePipe,
|
||||
DateComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -140,9 +145,15 @@ registerLocaleData(localeEnGb)
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: CsrfInterceptor,
|
||||
multi: true
|
||||
},{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: ApiVersionInterceptor,
|
||||
multi: true
|
||||
},
|
||||
FilterPipe,
|
||||
DocumentTitlePipe
|
||||
DocumentTitlePipe,
|
||||
{provide: NgbDateAdapter, useClass: ISODateTimeAdapter},
|
||||
{provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@@ -94,7 +94,7 @@
|
||||
</svg> {{d.title | documentTitle}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100" *ngIf="openDocuments.length > 1">
|
||||
<li class="nav-item w-100" *ngIf="openDocuments.length >= 1">
|
||||
<a class="nav-link text-truncate" [routerLink]="" (click)="closeAll()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
|
@@ -20,8 +20,17 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="date" class="form-control" id="date_after" [(ngModel)]="dateAfter" (change)="onChangeDebounce()">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()"
|
||||
[(ngModel)]="dateAfter" ngbDatepicker #dateAfterPicker="ngbDatepicker">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" (click)="dateAfterPicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
@@ -36,8 +45,17 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="date" class="form-control" id="date_before" [(ngModel)]="dateBefore" (change)="onChangeDebounce()">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()"
|
||||
[(ngModel)]="dateBefore" ngbDatepicker #dateBeforePicker="ngbDatepicker">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" (click)="dateBeforePicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
|
||||
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { SettingsService } from 'src/app/services/settings.service';
|
||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter';
|
||||
|
||||
export interface DateSelection {
|
||||
before?: string
|
||||
@@ -16,10 +19,17 @@ const LAST_YEAR = 3
|
||||
@Component({
|
||||
selector: 'app-date-dropdown',
|
||||
templateUrl: './date-dropdown.component.html',
|
||||
styleUrls: ['./date-dropdown.component.scss']
|
||||
styleUrls: ['./date-dropdown.component.scss'],
|
||||
providers: [
|
||||
{provide: NgbDateAdapter, useClass: ISODateAdapter},
|
||||
]
|
||||
})
|
||||
export class DateDropdownComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(settings: SettingsService) {
|
||||
this.datePlaceHolder = settings.getLocalizedDateInputFormat()
|
||||
}
|
||||
|
||||
quickFilters = [
|
||||
{id: LAST_7_DAYS, name: $localize`Last 7 days`},
|
||||
{id: LAST_MONTH, name: $localize`Last month`},
|
||||
@@ -27,6 +37,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
|
||||
{id: LAST_YEAR, name: $localize`Last year`}
|
||||
]
|
||||
|
||||
datePlaceHolder: string
|
||||
|
||||
@Input()
|
||||
dateBefore: string
|
||||
|
||||
|
@@ -1,13 +0,0 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group col">
|
||||
<label for="created_date">{{titleDate}}</label>
|
||||
<input type="date" class="form-control" id="created_date" [(ngModel)]="dateValue" (change)="dateOrTimeChanged()">
|
||||
</div>
|
||||
<div class="form-group col" *ngIf="titleTime">
|
||||
<label for="created_time">{{titleTime}}</label>
|
||||
<input type="time" class="form-control" id="created_time" [(ngModel)]="timeValue" (change)="dateOrTimeChanged()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> -->
|
@@ -1,61 +0,0 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DateTimeComponent),
|
||||
multi: true
|
||||
}],
|
||||
selector: 'app-input-date-time',
|
||||
templateUrl: './date-time.component.html',
|
||||
styleUrls: ['./date-time.component.scss']
|
||||
})
|
||||
export class DateTimeComponent implements OnInit,ControlValueAccessor {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
onChange = (newValue: any) => {};
|
||||
|
||||
onTouched = () => {};
|
||||
|
||||
writeValue(newValue: any): void {
|
||||
this.dateValue = formatDate(newValue, 'yyyy-MM-dd', "en-US")
|
||||
this.timeValue = formatDate(newValue, 'HH:mm:ss', 'en-US')
|
||||
}
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
@Input()
|
||||
titleDate: string = "Date"
|
||||
|
||||
@Input()
|
||||
titleTime: string
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false
|
||||
|
||||
@Input()
|
||||
hint: string
|
||||
|
||||
timeValue
|
||||
|
||||
dateValue
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
dateOrTimeChanged() {
|
||||
this.onChange(formatDate(this.dateValue + "T" + this.timeValue,"yyyy-MM-ddTHH:mm:ssZZZZZ", "en-us", "UTC"))
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" (dateSelect)="onChange(value)" (change)="onChange(value)"
|
||||
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invalid-feedback" i18n>Invalid date.</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
</div>
|
@@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DateTimeComponent } from './date-time.component';
|
||||
import { DateComponent } from './date.component';
|
||||
|
||||
describe('DateTimeComponent', () => {
|
||||
let component: DateTimeComponent;
|
||||
let fixture: ComponentFixture<DateTimeComponent>;
|
||||
describe('DateComponent', () => {
|
||||
let component: DateComponent;
|
||||
let fixture: ComponentFixture<DateComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DateTimeComponent ]
|
||||
declarations: [ DateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateTimeComponent);
|
||||
fixture = TestBed.createComponent(DateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@@ -0,0 +1,32 @@
|
||||
import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { NgbDateAdapter, NgbDateParserFormatter, NgbDatepickerContent } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SettingsService } from 'src/app/services/settings.service';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DateComponent),
|
||||
multi: true
|
||||
}],
|
||||
selector: 'app-input-date',
|
||||
templateUrl: './date.component.html',
|
||||
styleUrls: ['./date.component.scss']
|
||||
})
|
||||
export class DateComponent extends AbstractInputComponent<string> implements OnInit {
|
||||
|
||||
constructor(private settings: SettingsService) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit()
|
||||
this.placeholder = this.settings.getLocalizedDateInputFormat()
|
||||
}
|
||||
|
||||
placeholder: string
|
||||
|
||||
}
|
@@ -48,7 +48,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
||||
if (this.savedView.show_in_sidebar) {
|
||||
this.router.navigate(['view', this.savedView.id])
|
||||
} else {
|
||||
this.list.load(this.savedView)
|
||||
this.list.loadSavedView(this.savedView, true)
|
||||
this.router.navigate(["documents"])
|
||||
}
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@
|
||||
|
||||
<app-input-text #inputTitle i18n-title title="Title" formControlName="title" [error]="error?.title"></app-input-text>
|
||||
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
|
||||
<app-input-date-time i18n-titleDate titleDate="Date created" formControlName="created"></app-input-date-time>
|
||||
<app-input-date i18n-title title="Date created" formControlName="created" [error]="error?.created"></app-input-date>
|
||||
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
|
||||
(createNew)="createCorrespondent()" [suggestions]="suggestions?.correspondents"></app-input-select>
|
||||
<app-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true"
|
||||
|
@@ -191,8 +191,8 @@ export class DocumentDetailComponent implements OnInit {
|
||||
|
||||
close() {
|
||||
this.openDocumentService.closeDocument(this.document)
|
||||
if (this.documentListViewService.savedViewId) {
|
||||
this.router.navigate(['view', this.documentListViewService.savedViewId])
|
||||
if (this.documentListViewService.activeSavedViewId) {
|
||||
this.router.navigate(['view', this.documentListViewService.activeSavedViewId])
|
||||
} else {
|
||||
this.router.navigate(['documents'])
|
||||
}
|
||||
|
@@ -56,6 +56,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto ml-auto mb-2 mb-xl-0 d-flex">
|
||||
<div class="btn-group btn-group-sm mr-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" (click)="downloadSelected()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download" />
|
||||
</svg> <ng-container i18n>Download</ng-container>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" aria-label="Button group with nested dropdown">
|
||||
<button class="btn btn-outline-primary btn-sm dropdown-toggle-split" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||
<button ngbDropdownItem i18n (click)="downloadSelected('originals')">Download originals</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
|
@@ -15,6 +15,7 @@ import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable
|
||||
import { MatchingModel } from 'src/app/data/matching-model';
|
||||
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bulk-editor',
|
||||
@@ -137,7 +138,7 @@ export class BulkEditorComponent {
|
||||
} else {
|
||||
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on ${this.list.selected.size} selected document(s).`
|
||||
}
|
||||
|
||||
|
||||
modal.componentInstance.btnClass = "btn-warning"
|
||||
modal.componentInstance.btnCaption = $localize`Confirm`
|
||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||
@@ -207,4 +208,10 @@ export class BulkEditorComponent {
|
||||
this.executeBulkOperation(modal, "delete", {})
|
||||
})
|
||||
}
|
||||
|
||||
downloadSelected(content = "archive") {
|
||||
this.documentService.bulkDownload(Array.from(this.list.selected), content).subscribe((result: any) => {
|
||||
saveAs(result, 'documents.zip');
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -63,12 +63,12 @@
|
||||
<div class="btn-group ml-2 flex-fill" ngbDropdown role="group">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" ngbDropdownToggle i18n>Views</button>
|
||||
<div class="dropdown-menu shadow dropdown-menu-right" ngbDropdownMenu>
|
||||
<ng-container *ngIf="!list.savedViewId">
|
||||
<ng-container *ngIf="!list.activeSavedViewId">
|
||||
<button ngbDropdownItem *ngFor="let view of savedViewService.allViews" (click)="loadViewConfig(view)">{{view.name}}</button>
|
||||
<div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div>
|
||||
</ng-container>
|
||||
|
||||
<button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.savedViewId" i18n>Save "{{list.savedViewTitle}}"</button>
|
||||
<button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.activeSavedViewId" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
||||
<button ngbDropdownItem (click)="saveViewConfigAs()" i18n>Save as...</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@
|
||||
<span i18n *ngIf="list.selected.size == 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span> <span i18n *ngIf="isFiltered">(filtered)</span>
|
||||
</p>
|
||||
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" (pageChange)="list.reload()" aria-label="Default pagination"></ngb-pagination>
|
||||
[rotate]="true" aria-label="Default pagination"></ngb-pagination>
|
||||
</div>
|
||||
|
||||
<div *ngIf="displayMode == 'largeCards'">
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subscription } from 'rxjs';
|
||||
@@ -9,7 +9,7 @@ import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
||||
import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service';
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { FilterEditorComponent } from './filter-editor/filter-editor.component';
|
||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component';
|
||||
|
||||
@@ -46,7 +46,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.list.savedViewTitle || $localize`Documents`
|
||||
return this.list.activeSavedViewTitle || $localize`Documents`
|
||||
}
|
||||
|
||||
getSortFields() {
|
||||
@@ -73,19 +73,18 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
this.list.reload()
|
||||
})
|
||||
this.route.paramMap.subscribe(params => {
|
||||
this.list.clear()
|
||||
if (params.has('id')) {
|
||||
this.savedViewService.getCached(+params.get('id')).subscribe(view => {
|
||||
if (!view) {
|
||||
this.router.navigate(["404"])
|
||||
return
|
||||
}
|
||||
this.list.savedView = view
|
||||
this.list.activateSavedView(view)
|
||||
this.list.reload()
|
||||
this.rulesChanged()
|
||||
})
|
||||
} else {
|
||||
this.list.savedView = null
|
||||
this.list.activateSavedView(null)
|
||||
this.list.reload()
|
||||
this.rulesChanged()
|
||||
}
|
||||
@@ -99,16 +98,23 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
loadViewConfig(view: PaperlessSavedView) {
|
||||
this.list.load(view)
|
||||
this.list.loadSavedView(view)
|
||||
this.list.reload()
|
||||
this.rulesChanged()
|
||||
}
|
||||
|
||||
saveViewConfig() {
|
||||
this.savedViewService.update(this.list.savedView).subscribe(result => {
|
||||
this.toastService.showInfo($localize`View "${this.list.savedView.name}" saved successfully.`)
|
||||
})
|
||||
|
||||
if (this.list.activeSavedViewId != null) {
|
||||
let savedView: PaperlessSavedView = {
|
||||
id: this.list.activeSavedViewId,
|
||||
filter_rules: this.list.filterRules,
|
||||
sort_field: this.list.sortField,
|
||||
sort_reverse: this.list.sortReverse
|
||||
}
|
||||
this.savedViewService.patch(savedView).subscribe(result => {
|
||||
this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
saveViewConfigAs() {
|
||||
@@ -116,7 +122,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
modal.componentInstance.defaultName = this.filterEditor.generateFilterName()
|
||||
modal.componentInstance.saveClicked.subscribe(formValue => {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
let savedView = {
|
||||
let savedView: PaperlessSavedView = {
|
||||
name: formValue.name,
|
||||
show_on_dashboard: formValue.showOnDashboard,
|
||||
show_in_sidebar: formValue.showInSideBar,
|
||||
@@ -137,8 +143,8 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
|
||||
resetFilters(): void {
|
||||
this.filterRulesModified = false
|
||||
if (this.list.savedViewId) {
|
||||
this.savedViewService.getCached(this.list.savedViewId).subscribe(viewUntouched => {
|
||||
if (this.list.activeSavedViewId) {
|
||||
this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(viewUntouched => {
|
||||
this.list.filterRules = viewUntouched.filter_rules
|
||||
this.list.reload()
|
||||
})
|
||||
@@ -150,11 +156,11 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
|
||||
rulesChanged() {
|
||||
let modified = false
|
||||
if (this.list.savedView == null) {
|
||||
if (this.list.activeSavedViewId == null) {
|
||||
modified = this.list.filterRules.length > 0 // documents list is modified if it has any filters
|
||||
} else {
|
||||
// compare savedView current filters vs original
|
||||
this.savedViewService.getCached(this.list.savedViewId).subscribe(view => {
|
||||
this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(view => {
|
||||
let filterRulesInitial = view.filter_rules
|
||||
|
||||
if (this.list.filterRules.length !== filterRulesInitial.length) modified = true
|
||||
|
@@ -46,6 +46,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
return $localize`Without any tag`
|
||||
}
|
||||
|
||||
case FILTER_TITLE:
|
||||
return $localize`Title: ${rule.value}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +119,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
}
|
||||
|
||||
get filterRules() {
|
||||
get filterRules(): FilterRule[] {
|
||||
let filterRules: FilterRule[] = []
|
||||
if (this._titleFilter) {
|
||||
filterRules.push({rule_type: FILTER_TITLE, value: this._titleFilter})
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<div class="modal-body">
|
||||
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text>
|
||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text>
|
||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
||||
|
||||
</div>
|
||||
|
@@ -88,14 +88,15 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
get displayLanguageOptions(): LanguageOption[] {
|
||||
return [{code: "", name: $localize`Use system language`}].concat(this.settings.getLanguageOptions())
|
||||
return [
|
||||
{code: "", name: $localize`Use system language`}
|
||||
].concat(this.settings.getLanguageOptions())
|
||||
}
|
||||
|
||||
get dateLocaleOptions(): LanguageOption[] {
|
||||
return [
|
||||
{code: "", name: $localize`Use date format of display language`},
|
||||
{code: "iso-8601", name: $localize`ISO 8601`}
|
||||
].concat(this.settings.getLanguageOptions())
|
||||
{code: "", name: $localize`Use date format of display language`}
|
||||
].concat(this.settings.getDateLocaleOptions())
|
||||
}
|
||||
|
||||
get today() {
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
<app-input-check i18n-title title="Inbox tag" formControlName="is_inbox_tag" i18n-hint hint="Inbox tags are automatically assigned to all consumed documents."></app-input-check>
|
||||
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text>
|
||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
16
src-ui/src/app/interceptors/api-version.interceptor.spec.ts
Normal file
16
src-ui/src/app/interceptors/api-version.interceptor.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ApiVersionInterceptor } from './api-version.interceptor';
|
||||
|
||||
describe('ApiVersionInterceptor', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [
|
||||
ApiVersionInterceptor
|
||||
]
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
const interceptor: ApiVersionInterceptor = TestBed.inject(ApiVersionInterceptor);
|
||||
expect(interceptor).toBeTruthy();
|
||||
});
|
||||
});
|
25
src-ui/src/app/interceptors/api-version.interceptor.ts
Normal file
25
src-ui/src/app/interceptors/api-version.interceptor.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpHandler,
|
||||
HttpEvent,
|
||||
HttpInterceptor
|
||||
} from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Injectable()
|
||||
export class ApiVersionInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor() {}
|
||||
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
request = request.clone({
|
||||
setHeaders: {
|
||||
'Accept': `application/json; version=${environment.apiVersion}`
|
||||
}
|
||||
})
|
||||
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
@@ -13,17 +13,20 @@ const FORMAT_TO_ISO_FORMAT = {
|
||||
})
|
||||
export class CustomDatePipe extends DatePipe implements PipeTransform {
|
||||
|
||||
private defaultLocale: string
|
||||
|
||||
constructor(@Inject(LOCALE_ID) locale: string, private settings: SettingsService) {
|
||||
super(locale)
|
||||
this.defaultLocale = locale
|
||||
}
|
||||
|
||||
transform(value: any, format?: string, timezone?: string, locale?: string): string | null {
|
||||
let l = locale || this.settings.get(SETTINGS_KEYS.DATE_LOCALE)
|
||||
let l = locale || this.settings.get(SETTINGS_KEYS.DATE_LOCALE) || this.defaultLocale
|
||||
let f = format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT)
|
||||
if (l == "iso-8601") {
|
||||
return super.transform(value, FORMAT_TO_ISO_FORMAT[f], timezone)
|
||||
} else {
|
||||
return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, locale)
|
||||
return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, l)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,23 @@ import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys';
|
||||
import { DocumentService } from './rest/document.service';
|
||||
import { SettingsService, SETTINGS_KEYS } from './settings.service';
|
||||
|
||||
interface ListViewState {
|
||||
|
||||
title?: string
|
||||
|
||||
documents?: PaperlessDocument[]
|
||||
|
||||
currentPage: number
|
||||
collectionSize: number
|
||||
|
||||
sortField: string
|
||||
sortReverse: boolean
|
||||
|
||||
filterRules: FilterRule[]
|
||||
|
||||
selected?: Set<number>
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This service manages the document list which is displayed using the document list view.
|
||||
@@ -20,156 +37,174 @@ import { SettingsService, SETTINGS_KEYS } from './settings.service';
|
||||
})
|
||||
export class DocumentListViewService {
|
||||
|
||||
static DEFAULT_SORT_FIELD = 'created'
|
||||
|
||||
isReloading: boolean = false
|
||||
documents: PaperlessDocument[] = []
|
||||
currentPage = 1
|
||||
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
collectionSize: number
|
||||
|
||||
rangeSelectionAnchorIndex: number
|
||||
lastRangeSelectionToIndex: number
|
||||
|
||||
/**
|
||||
* This is the current config for the document list. The service will always remember the last settings used for the document list.
|
||||
*/
|
||||
private _documentListViewConfig: PaperlessSavedView
|
||||
/**
|
||||
* Optionally, this is the currently selected saved view, which might be null.
|
||||
*/
|
||||
private _savedViewConfig: PaperlessSavedView
|
||||
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
|
||||
get savedView(): PaperlessSavedView {
|
||||
return this._savedViewConfig
|
||||
private listViewStates: Map<number, ListViewState> = new Map()
|
||||
|
||||
private _activeSavedViewId: number = null
|
||||
|
||||
get activeSavedViewId() {
|
||||
return this._activeSavedViewId
|
||||
}
|
||||
|
||||
set savedView(value: PaperlessSavedView) {
|
||||
if (value && !this._savedViewConfig || value && value.id != this._savedViewConfig.id) {
|
||||
//saved view inactive and should be active now, or saved view active, but a different view is requested
|
||||
//this is here so that we don't modify value, which might be the actual instance of the saved view.
|
||||
this.selectNone()
|
||||
this._savedViewConfig = Object.assign({}, value)
|
||||
} else if (this._savedViewConfig && !value) {
|
||||
//saved view active, but document list requested
|
||||
this.selectNone()
|
||||
this._savedViewConfig = null
|
||||
get activeSavedViewTitle() {
|
||||
return this.activeListViewState.title
|
||||
}
|
||||
|
||||
private defaultListViewState(): ListViewState {
|
||||
return {
|
||||
title: null,
|
||||
documents: [],
|
||||
currentPage: 1,
|
||||
collectionSize: null,
|
||||
sortField: "created",
|
||||
sortReverse: true,
|
||||
filterRules: [],
|
||||
selected: new Set<number>()
|
||||
}
|
||||
}
|
||||
|
||||
get savedViewId() {
|
||||
return this.savedView?.id
|
||||
private get activeListViewState() {
|
||||
if (!this.listViewStates.has(this._activeSavedViewId)) {
|
||||
this.listViewStates.set(this._activeSavedViewId, this.defaultListViewState())
|
||||
}
|
||||
return this.listViewStates.get(this._activeSavedViewId)
|
||||
}
|
||||
|
||||
get savedViewTitle() {
|
||||
return this.savedView?.name
|
||||
}
|
||||
|
||||
get documentListView() {
|
||||
return this._documentListViewConfig
|
||||
}
|
||||
|
||||
set documentListView(value) {
|
||||
if (value) {
|
||||
this._documentListViewConfig = Object.assign({}, value)
|
||||
this.saveDocumentListView()
|
||||
activateSavedView(view: PaperlessSavedView) {
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
if (view) {
|
||||
this._activeSavedViewId = view.id
|
||||
this.loadSavedView(view)
|
||||
} else {
|
||||
this._activeSavedViewId = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is what switches between the saved views and the document list view. Everything on the document list uses
|
||||
* this property to determine the settings for the currently displayed document list.
|
||||
*/
|
||||
get view() {
|
||||
return this.savedView || this.documentListView
|
||||
}
|
||||
|
||||
load(view: PaperlessSavedView) {
|
||||
this.documentListView.filter_rules = cloneFilterRules(view.filter_rules)
|
||||
this.documentListView.sort_reverse = view.sort_reverse
|
||||
this.documentListView.sort_field = view.sort_field
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.collectionSize = null
|
||||
this.documents = []
|
||||
this.currentPage = 1
|
||||
loadSavedView(view: PaperlessSavedView, closeCurrentView: boolean = false) {
|
||||
if (closeCurrentView) {
|
||||
this._activeSavedViewId = null
|
||||
}
|
||||
this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules)
|
||||
this.activeListViewState.sortField = view.sort_field
|
||||
this.activeListViewState.sortReverse = view.sort_reverse
|
||||
if (this._activeSavedViewId) {
|
||||
this.activeListViewState.title = view.name
|
||||
}
|
||||
this.reduceSelectionToFilter()
|
||||
}
|
||||
|
||||
reload(onFinish?) {
|
||||
this.isReloading = true
|
||||
let activeListViewState = this.activeListViewState
|
||||
|
||||
this.documentService.listFiltered(
|
||||
this.currentPage,
|
||||
activeListViewState.currentPage,
|
||||
this.currentPageSize,
|
||||
this.view.sort_field,
|
||||
this.view.sort_reverse,
|
||||
this.view.filter_rules).subscribe(
|
||||
activeListViewState.sortField,
|
||||
activeListViewState.sortReverse,
|
||||
activeListViewState.filterRules).subscribe(
|
||||
result => {
|
||||
this.collectionSize = result.count
|
||||
this.documents = result.results
|
||||
this.isReloading = false
|
||||
activeListViewState.collectionSize = result.count
|
||||
activeListViewState.documents = result.results
|
||||
if (onFinish) {
|
||||
onFinish()
|
||||
}
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
this.isReloading = false
|
||||
},
|
||||
error => {
|
||||
if (this.currentPage != 1 && error.status == 404) {
|
||||
this.isReloading = false
|
||||
if (activeListViewState.currentPage != 1 && error.status == 404) {
|
||||
// this happens when applying a filter: the current page might not be available anymore due to the reduced result set.
|
||||
this.currentPage = 1
|
||||
activeListViewState.currentPage = 1
|
||||
this.reload()
|
||||
}
|
||||
this.isReloading = false
|
||||
})
|
||||
}
|
||||
|
||||
set filterRules(filterRules: FilterRule[]) {
|
||||
//we're going to clone the filterRules object, since we don't
|
||||
//want changes in the filter editor to propagate into here right away.
|
||||
this.view.filter_rules = filterRules
|
||||
this.activeListViewState.filterRules = filterRules
|
||||
this.reload()
|
||||
this.reduceSelectionToFilter()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get filterRules(): FilterRule[] {
|
||||
return this.view.filter_rules
|
||||
return this.activeListViewState.filterRules
|
||||
}
|
||||
|
||||
set sortField(field: string) {
|
||||
this.view.sort_field = field
|
||||
this.saveDocumentListView()
|
||||
this.activeListViewState.sortField = field
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get sortField(): string {
|
||||
return this.view.sort_field
|
||||
return this.activeListViewState.sortField
|
||||
}
|
||||
|
||||
set sortReverse(reverse: boolean) {
|
||||
this.view.sort_reverse = reverse
|
||||
this.saveDocumentListView()
|
||||
this.activeListViewState.sortReverse = reverse
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get sortReverse(): boolean {
|
||||
return this.view.sort_reverse
|
||||
return this.activeListViewState.sortReverse
|
||||
}
|
||||
|
||||
get collectionSize(): number {
|
||||
return this.activeListViewState.collectionSize
|
||||
}
|
||||
|
||||
get currentPage(): number {
|
||||
return this.activeListViewState.currentPage
|
||||
}
|
||||
|
||||
set currentPage(page: number) {
|
||||
this.activeListViewState.currentPage = page
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get documents(): PaperlessDocument[] {
|
||||
return this.activeListViewState.documents
|
||||
}
|
||||
|
||||
get selected(): Set<number> {
|
||||
return this.activeListViewState.selected
|
||||
}
|
||||
|
||||
setSort(field: string, reverse: boolean) {
|
||||
this.view.sort_field = field
|
||||
this.view.sort_reverse = reverse
|
||||
this.saveDocumentListView()
|
||||
this.activeListViewState.sortField = field
|
||||
this.activeListViewState.sortReverse = reverse
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
private saveDocumentListView() {
|
||||
sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(this.documentListView))
|
||||
if (this._activeSavedViewId == null) {
|
||||
let savedState: ListViewState = {
|
||||
collectionSize: this.activeListViewState.collectionSize,
|
||||
currentPage: this.activeListViewState.currentPage,
|
||||
filterRules: this.activeListViewState.filterRules,
|
||||
sortField: this.activeListViewState.sortField,
|
||||
sortReverse: this.activeListViewState.sortReverse
|
||||
}
|
||||
sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(savedState))
|
||||
}
|
||||
}
|
||||
|
||||
quickFilter(filterRules: FilterRule[]) {
|
||||
this.savedView = null
|
||||
this.view.filter_rules = filterRules
|
||||
this._activeSavedViewId = null
|
||||
this.activeListViewState.filterRules = filterRules
|
||||
this.activeListViewState.currentPage = 1
|
||||
this.reduceSelectionToFilter()
|
||||
this.saveDocumentListView()
|
||||
this.router.navigate(["documents"])
|
||||
@@ -217,8 +252,6 @@ export class DocumentListViewService {
|
||||
}
|
||||
}
|
||||
|
||||
selected = new Set<number>()
|
||||
|
||||
selectNone() {
|
||||
this.selected.clear()
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
@@ -227,13 +260,11 @@ export class DocumentListViewService {
|
||||
reduceSelectionToFilter() {
|
||||
if (this.selected.size > 0) {
|
||||
this.documentService.listAllFilteredIds(this.filterRules).subscribe(ids => {
|
||||
let subset = new Set<number>()
|
||||
for (let id of ids) {
|
||||
if (this.selected.has(id)) {
|
||||
subset.add(id)
|
||||
for (let id of this.selected) {
|
||||
if (!ids.includes(id)) {
|
||||
this.selected.delete(id)
|
||||
}
|
||||
}
|
||||
this.selected = subset
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -287,20 +318,21 @@ export class DocumentListViewService {
|
||||
}
|
||||
|
||||
constructor(private documentService: DocumentService, private settings: SettingsService, private router: Router) {
|
||||
let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
|
||||
let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
|
||||
if (documentListViewConfigJson) {
|
||||
try {
|
||||
this.documentListView = JSON.parse(documentListViewConfigJson)
|
||||
let savedState: ListViewState = JSON.parse(documentListViewConfigJson)
|
||||
// Remove null elements from the restored state
|
||||
Object.keys(savedState).forEach(k => {
|
||||
if (savedState[k] == null) {
|
||||
delete savedState[k]
|
||||
}
|
||||
})
|
||||
//only use restored state attributes instead of defaults if they are not null
|
||||
let newState = Object.assign(this.defaultListViewState(), savedState)
|
||||
this.listViewStates.set(null, newState)
|
||||
} catch (e) {
|
||||
sessionStorage.removeItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
|
||||
this.documentListView = null
|
||||
}
|
||||
}
|
||||
if (!this.documentListView || this.documentListView.filter_rules == null || this.documentListView.sort_reverse == null || this.documentListView.sort_field == null) {
|
||||
this.documentListView = {
|
||||
filter_rules: [],
|
||||
sort_reverse: true,
|
||||
sort_field: 'created'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -134,4 +134,8 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
|
||||
return this.http.get<PaperlessDocumentSuggestions>(this.getResourceUrl(id, 'suggestions'))
|
||||
}
|
||||
|
||||
bulkDownload(ids: number[], content="both") {
|
||||
return this.http.post(this.getResourceUrl(null, 'bulk_download'), {"documents": ids, "content": content}, { responseType: 'blob' })
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
||||
import { Inject, Injectable, LOCALE_ID, Renderer2, RendererFactory2 } from '@angular/core';
|
||||
import { Meta } from '@angular/platform-browser';
|
||||
import { CookieService } from 'ngx-cookie-service';
|
||||
|
||||
@@ -10,9 +10,14 @@ export interface PaperlessSettings {
|
||||
}
|
||||
|
||||
export interface LanguageOption {
|
||||
code: string,
|
||||
name: string,
|
||||
code: string
|
||||
name: string
|
||||
englishName?: string
|
||||
|
||||
/**
|
||||
* A date format string for use by the date selectors. MUST contain 'yyyy', 'mm' and 'dd'.
|
||||
*/
|
||||
dateInputFormat?: string
|
||||
}
|
||||
|
||||
export const SETTINGS_KEYS = {
|
||||
@@ -56,7 +61,8 @@ export class SettingsService {
|
||||
private rendererFactory: RendererFactory2,
|
||||
@Inject(DOCUMENT) private document,
|
||||
private cookieService: CookieService,
|
||||
private meta: Meta
|
||||
private meta: Meta,
|
||||
@Inject(LOCALE_ID) private localeId: string
|
||||
) {
|
||||
this.renderer = rendererFactory.createRenderer(null, null);
|
||||
|
||||
@@ -79,14 +85,20 @@ export class SettingsService {
|
||||
|
||||
getLanguageOptions(): LanguageOption[] {
|
||||
return [
|
||||
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)"},
|
||||
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)"},
|
||||
{code: "de", name: $localize`German`, englishName: "German"},
|
||||
{code: "nl", name: $localize`Dutch`, englishName: "Dutch"},
|
||||
{code: "fr", name: $localize`French`, englishName: "French"}
|
||||
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
|
||||
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
|
||||
{code: "de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
|
||||
{code: "nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
|
||||
{code: "fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
|
||||
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"}
|
||||
]
|
||||
}
|
||||
|
||||
getDateLocaleOptions(): LanguageOption[] {
|
||||
let isoOption: LanguageOption = {code: "iso-8601", name: $localize`ISO 8601`, dateInputFormat: "yyyy-mm-dd"}
|
||||
return [isoOption].concat(this.getLanguageOptions())
|
||||
}
|
||||
|
||||
private getLanguageCookieName() {
|
||||
let prefix = ""
|
||||
if (this.meta.getTag('name=cookie_prefix')) {
|
||||
@@ -107,6 +119,11 @@ export class SettingsService {
|
||||
}
|
||||
}
|
||||
|
||||
getLocalizedDateInputFormat(): string {
|
||||
let dateLocale = this.get(SETTINGS_KEYS.DATE_LOCALE) || this.getLanguage() || this.localeId.toLowerCase()
|
||||
return this.getDateLocaleOptions().find(o => o.code == dateLocale)?.dateInputFormat || "yyyy-mm-dd"
|
||||
}
|
||||
|
||||
get(key: string): any {
|
||||
let setting = SETTINGS.find(s => s.key == key)
|
||||
|
||||
|
59
src-ui/src/app/utils/ngb-date-parser-formatter.ts
Normal file
59
src-ui/src/app/utils/ngb-date-parser-formatter.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Injectable } from "@angular/core"
|
||||
import { NgbDateParserFormatter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap"
|
||||
import { SettingsService } from "../services/settings.service"
|
||||
|
||||
@Injectable()
|
||||
export class LocalizedDateParserFormatter extends NgbDateParserFormatter {
|
||||
|
||||
constructor(private settings: SettingsService) {
|
||||
super()
|
||||
}
|
||||
|
||||
private getDateInputFormat() {
|
||||
return this.settings.getLocalizedDateInputFormat()
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs a regular expression from a date input format which is then
|
||||
* used to parse dates.
|
||||
*/
|
||||
private getDateParseRegex() {
|
||||
return new RegExp(
|
||||
"^" + this.getDateInputFormat()
|
||||
.replace('dd', '(?<day>[0-9]+)')
|
||||
.replace('mm', '(?<month>[0-9]+)')
|
||||
.replace('yyyy', '(?<year>[0-9]+)')
|
||||
.split('.').join('\\.\\s*') + "$" // allow whitespace(s) after dot (specific for German)
|
||||
)
|
||||
}
|
||||
|
||||
parse(value: string): NgbDateStruct | null {
|
||||
let match = this.getDateParseRegex().exec(value)
|
||||
if (match) {
|
||||
let dateStruct = {
|
||||
day: +match.groups.day,
|
||||
month: +match.groups.month,
|
||||
year: +match.groups.year
|
||||
}
|
||||
if (dateStruct.year <= (new Date().getFullYear() - 2000)) {
|
||||
dateStruct.year += 2000
|
||||
} else if (dateStruct.year < 100) {
|
||||
dateStruct.year += 1900
|
||||
}
|
||||
return dateStruct
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
format(date: NgbDateStruct | null): string {
|
||||
if (date) {
|
||||
return this.getDateInputFormat()
|
||||
.replace('dd', date.day.toString().padStart(2, '0'))
|
||||
.replace('mm', date.month.toString().padStart(2, '0'))
|
||||
.replace('yyyy', date.year.toString().padStart(4, '0'))
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
27
src-ui/src/app/utils/ngb-iso-date-adapter.ts
Normal file
27
src-ui/src/app/utils/ngb-iso-date-adapter.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { NgbDateAdapter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";
|
||||
|
||||
@Injectable()
|
||||
export class ISODateAdapter extends NgbDateAdapter<string> {
|
||||
|
||||
fromModel(value: string | null): NgbDateStruct | null {
|
||||
if (value) {
|
||||
let date = new Date(value)
|
||||
return {
|
||||
day : date.getDate(),
|
||||
month : date.getMonth() + 1,
|
||||
year : date.getFullYear()
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
toModel(date: NgbDateStruct | null): string | null {
|
||||
if (date) {
|
||||
return date.year.toString().padStart(4, '0') + "-" + date.month.toString().padStart(2, '0') + "-" + date.day.toString().padStart(2, '0')
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
23
src-ui/src/app/utils/ngb-iso-date-time-adapter.ts
Normal file
23
src-ui/src/app/utils/ngb-iso-date-time-adapter.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { NgbDateAdapter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";
|
||||
|
||||
@Injectable()
|
||||
export class ISODateTimeAdapter extends NgbDateAdapter<string> {
|
||||
|
||||
fromModel(value: string | null): NgbDateStruct | null {
|
||||
if (value) {
|
||||
let date = new Date(value)
|
||||
return {
|
||||
day : date.getDate(),
|
||||
month : date.getMonth() + 1,
|
||||
year : date.getFullYear()
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
toModel(date: NgbDateStruct | null): string | null {
|
||||
return date ? new Date(date.year, date.month - 1, date.day).toISOString() : null
|
||||
}
|
||||
}
|
BIN
src-ui/src/apple-touch-icon.png
Normal file
BIN
src-ui/src/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
@@ -1,8 +1,9 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiBaseUrl: "/api/",
|
||||
apiVersion: "1",
|
||||
appTitle: "Paperless-ng",
|
||||
version: "1.1.4",
|
||||
version: "1.2.1",
|
||||
webSocketHost: window.location.host,
|
||||
webSocketProtocol: (window.location.protocol == "https:" ? "wss:" : "ws:")
|
||||
};
|
||||
|
@@ -5,6 +5,7 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiBaseUrl: "http://localhost:8000/api/",
|
||||
apiVersion: "1",
|
||||
appTitle: "Paperless-ng",
|
||||
version: "DEVELOPMENT",
|
||||
webSocketHost: "localhost:8000",
|
||||
|
@@ -9,6 +9,7 @@
|
||||
<meta name="theme-color" content="#17541f" />
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||
</head>
|
||||
<body class="color-scheme-system">
|
||||
<app-root></app-root>
|
||||
|
@@ -58,11 +58,11 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2155249406916744630">
|
||||
<source>View "<x equiv-text="this.list.savedView.name" id="PH"/>" saved successfully.</source>
|
||||
<target>Ansicht "<x equiv-text="this.list.savedView.name" id="PH"/>" erfolgreich gespeichert.</target>
|
||||
<source>View "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" saved successfully.</source>
|
||||
<target>Ansicht "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" erfolgreich gespeichert.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6837554170707123455">
|
||||
@@ -70,7 +70,7 @@
|
||||
<target>Ansicht "<x equiv-text="savedView.name" id="PH"/>" erfolgreich erstellt.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
|
||||
@@ -129,9 +129,9 @@
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="72e7d343f9165602cce1ca7faffbc565fd31ef92">
|
||||
<source>Save "<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>"<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>" speichern</target>
|
||||
<trans-unit datatype="html" id="5f5ce787c428d917c30c9bd70789a618e09743a7">
|
||||
<source>Save "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>"<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>" speichern</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
@@ -585,14 +585,6 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5ca707824ab93066c7d9b44e1b8bf216725c2c22">
|
||||
<source>Filter</source>
|
||||
<target>Filtern</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5610279464668232148">
|
||||
<source>Saved view "<x equiv-text="savedView.name" id="PH"/>" deleted.</source>
|
||||
<target>Gespeicherte Ansicht "<x equiv-text="savedView.name" id="PH"/>" gelöscht.</target>
|
||||
@@ -614,7 +606,7 @@
|
||||
<target>Benutze Systemsprache</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7729897675462249787">
|
||||
@@ -622,7 +614,7 @@
|
||||
<target>Benutze Datumsformat der Anzeigesprache</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">95</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8488620293789898901">
|
||||
@@ -630,7 +622,7 @@
|
||||
<target>Fehler beim Speichern der Einstellungen auf dem Server: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
|
||||
@@ -1234,6 +1226,14 @@
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6523384805359286307">
|
||||
<source>Title: <x equiv-text="rule.value" id="PH"/></source>
|
||||
<target>Titel: <x equiv-text="rule.value" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="02d184c288f567825a1fcbf83bcd3099a10853d5">
|
||||
<source>Filter tags</source>
|
||||
<target>Tags filtern</target>
|
||||
@@ -1392,7 +1392,7 @@
|
||||
<target>Fehler beim Ausführung der Massenverarbeitung: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7894972847287473517">
|
||||
@@ -1400,7 +1400,7 @@
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">113</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8639884465898458690">
|
||||
@@ -1408,7 +1408,7 @@
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>" und "<x equiv-text="items[1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">This is for messages like 'modify "tag1" and "tag2"'</note>
|
||||
</trans-unit>
|
||||
@@ -1417,7 +1417,7 @@
|
||||
<target>, </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is used to separate enumerations and should probably be a comma and a whitespace in most languages</note>
|
||||
</trans-unit>
|
||||
@@ -1426,7 +1426,7 @@
|
||||
<target><x equiv-text="list" id="PH"/> und "<x equiv-text="items[items.length - 1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note>
|
||||
</trans-unit>
|
||||
@@ -1435,7 +1435,7 @@
|
||||
<target>Tag-Zuweisung bestätigen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6619516195038467207">
|
||||
@@ -1443,7 +1443,7 @@
|
||||
<target>Diese Aktion wird <x equiv-text="this.list.selected.size" id="PH_1"/> ausgewählten Dokumenten das Tag "<x equiv-text="tag.name" id="PH"/>" hinzufügen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1894412783609570695">
|
||||
@@ -1451,7 +1451,7 @@
|
||||
<target>Diese Aktion wird <x equiv-text="this.list.selected.size" id="PH_1"/> ausgewählten Dokumenten die Tags <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> hinzufügen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7181166515756808573">
|
||||
@@ -1459,7 +1459,7 @@
|
||||
<target>Diese Aktion wird das Tag "<x equiv-text="tag.name" id="PH"/>" von <x equiv-text="this.list.selected.size" id="PH_1"/> ausgewählten Dokumenten entfernen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3819792277998068944">
|
||||
@@ -1467,7 +1467,7 @@
|
||||
<target>Diese Aktion wird die Tags <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH"/> von <x equiv-text="this.list.selected.size" id="PH_1"/> ausgewählten Dokumenten entfernen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
<context context-type="linenumber">137</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2739066218579571288">
|
||||
@@ -1475,7 +1475,7 @@
|
||||
<target>Diese Aktion wird die Tags <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> den <x equiv-text="this.list.selected.size" id="PH_2"/> ausgewählten Dokumenten hinzufügen und die Tags <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH_1"/> entfernen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
<context context-type="linenumber">139</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2996713129519325161">
|
||||
@@ -1483,7 +1483,7 @@
|
||||
<target>Korrespondent-Zuweisung bestätigen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">158</context>
|
||||
<context context-type="linenumber">159</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6900893559485781849">
|
||||
@@ -1491,7 +1491,7 @@
|
||||
<target>Diese Aktion wird <x equiv-text="this.list.selected.size" id="PH_1"/> ausgewählten Dokumenten den Korrespondent "<x equiv-text="correspondent.name" id="PH"/>" zuweisen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">160</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1257522660364398440">
|
||||
@@ -1499,7 +1499,7 @@
|
||||
<target>Diese Aktion wird bei <x equiv-text="this.list.selected.size" id="PH"/> ausgewählten Dokumenten den Korrespondent entfernen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5393409374423140648">
|
||||
@@ -1507,7 +1507,7 @@
|
||||
<target>Dokumenttyp-Zuweisung bestätigen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="332180123895325027">
|
||||
@@ -1515,7 +1515,7 @@
|
||||
<target>Diese Aktion wird <x equiv-text="this.list.selected.size" id="PH_1"/> ausgewählten Dokumenten den Dokumenttyp "<x equiv-text="correspondent.name" id="PH"/>" zuweisen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
<context context-type="linenumber">184</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2236642492594872779">
|
||||
@@ -1523,7 +1523,7 @@
|
||||
<target>Diese Aktion wird bei <x equiv-text="this.list.selected.size" id="PH"/> ausgewählten Dokumenten den Dokumenttyp entfernen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">185</context>
|
||||
<context context-type="linenumber">186</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="749430623564850405">
|
||||
@@ -1531,7 +1531,7 @@
|
||||
<target>Löschen bestätigen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">200</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4303174930844518780">
|
||||
@@ -1539,7 +1539,7 @@
|
||||
<target>Diese Aktion wird <x equiv-text="this.list.selected.size" id="PH"/> ausgewählte Dokumente unwiderruflich löschen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5641451190833696892">
|
||||
@@ -1547,7 +1547,7 @@
|
||||
<target>Diese Aktion kann nicht rückgängig gemacht werden.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6734339521247847366">
|
||||
@@ -1555,7 +1555,7 @@
|
||||
<target>Dokument(e) löschen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8b0609df23817024b3bed12beb9b64fc1009f588">
|
||||
@@ -1582,6 +1582,14 @@
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="fc2de37422d7c4af6686842283cc2afd781b6848">
|
||||
<source>Download originals</source>
|
||||
<target>Originale herunterladen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
|
||||
<source>Suggestions:</source>
|
||||
<target>Vorschläge:</target>
|
||||
@@ -1614,22 +1622,22 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="46c8fe557cf52c9389783627d4f85453f4ddb459">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Dokumente im Posteingang: <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="c327c0e67bcac7494dcbaa9afb3b42d5008c6438">
|
||||
<source>Total documents: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Anzahl Dokumente gesamt: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<trans-unit datatype="html" id="c0d907c2687c09612395aee6ef7c04ca8e5e5e0a">
|
||||
<source>Total documents: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Anzahl Dokumente gesamt: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="13e8d49dbcad9f9d71e66a9a56d6f328cff430c9">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Dokumente im Posteingang: <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6443586946875325554">
|
||||
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
|
||||
<target>Verarbeite: <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
|
||||
@@ -1816,6 +1824,14 @@
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="d6529debfc1613db22d6fa096ebfeb8a85fa739d">
|
||||
<source>Invalid date.</source>
|
||||
<target>Ungültiges Datum.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2807800733729323332">
|
||||
<source>Yes</source>
|
||||
<target>Ja</target>
|
||||
@@ -1845,7 +1861,15 @@
|
||||
<target>Englisch (US)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6987083569809053351">
|
||||
<source>English (GB)</source>
|
||||
<target>Englisch (UK)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1858110241312746425">
|
||||
@@ -1853,7 +1877,7 @@
|
||||
<target>Deutsch</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3071065188816255493">
|
||||
@@ -1861,7 +1885,7 @@
|
||||
<target>Niederländisch</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7633754075223722162">
|
||||
@@ -1869,7 +1893,23 @@
|
||||
<target>Französisch</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9184513005098760425">
|
||||
<source>Portuguese (Brazil)</source>
|
||||
<target>Portugiesisch (Brasilien)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4912706592792948707">
|
||||
<source>ISO 8601</source>
|
||||
<target>ISO 8601</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2119857572761283468">
|
||||
|
@@ -58,19 +58,19 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2155249406916744630">
|
||||
<source>View "<x equiv-text="this.list.savedView.name" id="PH"/>" saved successfully.</source>
|
||||
<target>View &quot;<x equiv-text="this.list.savedView.name" id="PH"/>&quot; saved successfully.</target>
|
||||
<source>View "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" saved successfully.</source>
|
||||
<target>View "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" saved successfully.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6837554170707123455">
|
||||
<source>View "<x equiv-text="savedView.name" id="PH"/>" created successfully.</source>
|
||||
<target>View &quot;<x equiv-text="savedView.name" id="PH"/>&quot; created successfully.</target>
|
||||
<target>View "<x equiv-text="savedView.name" id="PH"/>" created successfully.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
|
||||
@@ -129,9 +129,9 @@
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="72e7d343f9165602cce1ca7faffbc565fd31ef92">
|
||||
<source>Save "<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>Save &quot;<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>&quot;</target>
|
||||
<trans-unit datatype="html" id="5f5ce787c428d917c30c9bd70789a618e09743a7">
|
||||
<source>Save "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>Save "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
@@ -219,7 +219,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5382975254277698192">
|
||||
<source>Do you really want to delete document "<x equiv-text="this.document.title" id="PH"/>"?</source>
|
||||
<target>Do you really want to delete document &quot;<x equiv-text="this.document.title" id="PH"/>&quot;?</target>
|
||||
<target>Do you really want to delete document "<x equiv-text="this.document.title" id="PH"/>"?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
@@ -475,7 +475,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="93754014749412887">
|
||||
<source>Do you really want to delete the tag "<x equiv-text="object.name" id="PH"/>"?</source>
|
||||
<target>Do you really want to delete the tag &quot;<x equiv-text="object.name" id="PH"/>&quot;?</target>
|
||||
<target>Do you really want to delete the tag "<x equiv-text="object.name" id="PH"/>"?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.ts</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
@@ -563,7 +563,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4990731724078522539">
|
||||
<source>Do you really want to delete the document type "<x equiv-text="object.name" id="PH"/>"?</source>
|
||||
<target>Do you really want to delete the document type &quot;<x equiv-text="object.name" id="PH"/>&quot;?</target>
|
||||
<target>Do you really want to delete the document type "<x equiv-text="object.name" id="PH"/>"?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/document-type-list/document-type-list.component.ts</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
@@ -585,17 +585,9 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5ca707824ab93066c7d9b44e1b8bf216725c2c22">
|
||||
<source>Filter</source>
|
||||
<target>Filter</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5610279464668232148">
|
||||
<source>Saved view "<x equiv-text="savedView.name" id="PH"/>" deleted.</source>
|
||||
<target>Saved view &quot;<x equiv-text="savedView.name" id="PH"/>&quot; deleted.</target>
|
||||
<target>Saved view "<x equiv-text="savedView.name" id="PH"/>" deleted.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">67</context>
|
||||
@@ -614,7 +606,7 @@
|
||||
<target>Use system language</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7729897675462249787">
|
||||
@@ -622,7 +614,7 @@
|
||||
<target>Use date format of display language</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">95</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8488620293789898901">
|
||||
@@ -630,7 +622,7 @@
|
||||
<target>Error while storing settings on server: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
|
||||
@@ -907,7 +899,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7427874343955308724">
|
||||
<source>Do you really want to delete the correspondent "<x equiv-text="object.name" id="PH"/>"?</source>
|
||||
<target>Do you really want to delete the correspondent &quot;<x equiv-text="object.name" id="PH"/>&quot;?</target>
|
||||
<target>Do you really want to delete the correspondent "<x equiv-text="object.name" id="PH"/>"?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/correspondent-list/correspondent-list.component.ts</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
@@ -1075,7 +1067,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="afa760e48c97d64d19c1455d18b7834a2256e23f">
|
||||
<source>Did you mean "<x equiv-text="<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}" id="START_LINK"/><x equiv-text="{{correctedQuery}}</a>" id="INTERPOLATION"/><x equiv-text="</a>" id="CLOSE_LINK"/>"?</source>
|
||||
<target>Did you mean &quot;<x equiv-text="<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}" id="START_LINK"/><x equiv-text="{{correctedQuery}}</a>" id="INTERPOLATION"/><x equiv-text="</a>" id="CLOSE_LINK"/>&quot;?</target>
|
||||
<target>Did you mean "<x equiv-text="<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}" id="START_LINK"/><x equiv-text="{{correctedQuery}}</a>" id="INTERPOLATION"/><x equiv-text="</a>" id="CLOSE_LINK"/>"?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -1234,6 +1226,14 @@
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6523384805359286307">
|
||||
<source>Title: <x equiv-text="rule.value" id="PH"/></source>
|
||||
<target>Title: <x equiv-text="rule.value" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="02d184c288f567825a1fcbf83bcd3099a10853d5">
|
||||
<source>Filter tags</source>
|
||||
<target>Filter tags</target>
|
||||
@@ -1392,23 +1392,23 @@
|
||||
<target>Error executing bulk operation: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7894972847287473517">
|
||||
<source>"<x equiv-text="items[0].name" id="PH"/>"</source>
|
||||
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</target>
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">113</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8639884465898458690">
|
||||
<source>"<x equiv-text="items[0].name" id="PH"/>" and "<x equiv-text="items[1].name" id="PH_1"/>"</source>
|
||||
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; and &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</target>
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>" and "<x equiv-text="items[1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">This is for messages like 'modify "tag1" and "tag2"'</note>
|
||||
</trans-unit>
|
||||
@@ -1417,16 +1417,16 @@
|
||||
<target>, </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is used to separate enumerations and should probably be a comma and a whitespace in most languages</note>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1822679894391095557">
|
||||
<source><x equiv-text="list" id="PH"/> and "<x equiv-text="items[items.length - 1].name" id="PH_1"/>"</source>
|
||||
<target><x equiv-text="list" id="PH"/> and &quot;<x equiv-text="items[items.length - 1].name" id="PH_1"/>&quot;</target>
|
||||
<target><x equiv-text="list" id="PH"/> and "<x equiv-text="items[items.length - 1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note>
|
||||
</trans-unit>
|
||||
@@ -1435,15 +1435,15 @@
|
||||
<target>Confirm tags assignment</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6619516195038467207">
|
||||
<source>This operation will add the tag "<x equiv-text="tag.name" id="PH"/>" to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</source>
|
||||
<target>This operation will add the tag &quot;<x equiv-text="tag.name" id="PH"/>&quot; to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<target>This operation will add the tag "<x equiv-text="tag.name" id="PH"/>" to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1894412783609570695">
|
||||
@@ -1451,15 +1451,15 @@
|
||||
<target>This operation will add the tags <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7181166515756808573">
|
||||
<source>This operation will remove the tag "<x equiv-text="tag.name" id="PH"/>" from <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</source>
|
||||
<target>This operation will remove the tag &quot;<x equiv-text="tag.name" id="PH"/>&quot; from <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<target>This operation will remove the tag "<x equiv-text="tag.name" id="PH"/>" from <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3819792277998068944">
|
||||
@@ -1467,7 +1467,7 @@
|
||||
<target>This operation will remove the tags <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH"/> from <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
<context context-type="linenumber">137</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2739066218579571288">
|
||||
@@ -1475,7 +1475,7 @@
|
||||
<target>This operation will add the tags <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> and remove the tags <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH_1"/> on <x equiv-text="this.list.selected.size" id="PH_2"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
<context context-type="linenumber">139</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2996713129519325161">
|
||||
@@ -1483,15 +1483,15 @@
|
||||
<target>Confirm correspondent assignment</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">158</context>
|
||||
<context context-type="linenumber">159</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6900893559485781849">
|
||||
<source>This operation will assign the correspondent "<x equiv-text="correspondent.name" id="PH"/>" to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</source>
|
||||
<target>This operation will assign the correspondent &quot;<x equiv-text="correspondent.name" id="PH"/>&quot; to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<target>This operation will assign the correspondent "<x equiv-text="correspondent.name" id="PH"/>" to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">160</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1257522660364398440">
|
||||
@@ -1499,7 +1499,7 @@
|
||||
<target>This operation will remove the correspondent from <x equiv-text="this.list.selected.size" id="PH"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5393409374423140648">
|
||||
@@ -1507,15 +1507,15 @@
|
||||
<target>Confirm document type assignment</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="332180123895325027">
|
||||
<source>This operation will assign the document type "<x equiv-text="documentType.name" id="PH"/>" to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</source>
|
||||
<target>This operation will assign the document type &quot;<x equiv-text="documentType.name" id="PH"/>&quot; to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<target>This operation will assign the document type "<x equiv-text="documentType.name" id="PH"/>" to <x equiv-text="this.list.selected.size" id="PH_1"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
<context context-type="linenumber">184</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2236642492594872779">
|
||||
@@ -1523,7 +1523,7 @@
|
||||
<target>This operation will remove the document type from <x equiv-text="this.list.selected.size" id="PH"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">185</context>
|
||||
<context context-type="linenumber">186</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="749430623564850405">
|
||||
@@ -1531,7 +1531,7 @@
|
||||
<target>Delete confirm</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">200</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4303174930844518780">
|
||||
@@ -1539,7 +1539,7 @@
|
||||
<target>This operation will permanently delete <x equiv-text="this.list.selected.size" id="PH"/> selected document(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5641451190833696892">
|
||||
@@ -1547,7 +1547,7 @@
|
||||
<target>This operation cannot be undone.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6734339521247847366">
|
||||
@@ -1555,7 +1555,7 @@
|
||||
<target>Delete document(s)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8b0609df23817024b3bed12beb9b64fc1009f588">
|
||||
@@ -1582,6 +1582,14 @@
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="fc2de37422d7c4af6686842283cc2afd781b6848">
|
||||
<source>Download originals</source>
|
||||
<target>Download originals</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
|
||||
<source>Suggestions:</source>
|
||||
<target>Suggestions:</target>
|
||||
@@ -1614,22 +1622,22 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="46c8fe557cf52c9389783627d4f85453f4ddb459">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Documents in inbox: <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="c327c0e67bcac7494dcbaa9afb3b42d5008c6438">
|
||||
<source>Total documents: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Total documents: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<trans-unit datatype="html" id="c0d907c2687c09612395aee6ef7c04ca8e5e5e0a">
|
||||
<source>Total documents: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Total documents: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="13e8d49dbcad9f9d71e66a9a56d6f328cff430c9">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Documents in inbox: <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6443586946875325554">
|
||||
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
|
||||
<target>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
|
||||
@@ -1816,6 +1824,14 @@
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="d6529debfc1613db22d6fa096ebfeb8a85fa739d">
|
||||
<source>Invalid date.</source>
|
||||
<target>Invalid date.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2807800733729323332">
|
||||
<source>Yes</source>
|
||||
<target>Yes</target>
|
||||
@@ -1845,7 +1861,15 @@
|
||||
<target>English (US)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6987083569809053351">
|
||||
<source>English (GB)</source>
|
||||
<target>English (GB)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1858110241312746425">
|
||||
@@ -1853,7 +1877,7 @@
|
||||
<target>German</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3071065188816255493">
|
||||
@@ -1861,7 +1885,7 @@
|
||||
<target>Dutch</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7633754075223722162">
|
||||
@@ -1869,7 +1893,23 @@
|
||||
<target>French</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9184513005098760425">
|
||||
<source>Portuguese (Brazil)</source>
|
||||
<target>Portuguese (Brazil)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4912706592792948707">
|
||||
<source>ISO 8601</source>
|
||||
<target>ISO 8601</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2119857572761283468">
|
||||
|
@@ -58,11 +58,11 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2155249406916744630">
|
||||
<source>View "<x equiv-text="this.list.savedView.name" id="PH"/>" saved successfully.</source>
|
||||
<target>Vue "<x equiv-text="this.list.savedView.name" id="PH"/>" enregistrée avec succès.</target>
|
||||
<source>View "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" saved successfully.</source>
|
||||
<target>Vue "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" enregistrée avec succès.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6837554170707123455">
|
||||
@@ -70,7 +70,7 @@
|
||||
<target>Vue "<x equiv-text="savedView.name" id="PH"/>" créée avec succès.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
|
||||
@@ -129,9 +129,9 @@
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="72e7d343f9165602cce1ca7faffbc565fd31ef92">
|
||||
<source>Save "<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>Enregistrer "<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>"</target>
|
||||
<trans-unit datatype="html" id="5f5ce787c428d917c30c9bd70789a618e09743a7">
|
||||
<source>Save "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>Enregistrer "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
@@ -585,14 +585,6 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5ca707824ab93066c7d9b44e1b8bf216725c2c22">
|
||||
<source>Filter</source>
|
||||
<target>Filtrer</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5610279464668232148">
|
||||
<source>Saved view "<x equiv-text="savedView.name" id="PH"/>" deleted.</source>
|
||||
<target>Vue "<x equiv-text="savedView.name" id="PH"/>" supprimée.</target>
|
||||
@@ -614,7 +606,7 @@
|
||||
<target>Utiliser la langue du système</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7729897675462249787">
|
||||
@@ -622,7 +614,7 @@
|
||||
<target>Utiliser le format de date de la langue d'affichage</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">95</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8488620293789898901">
|
||||
@@ -630,7 +622,7 @@
|
||||
<target>Une erreur s'est produite lors de l'enregistrement des paramètres sur le serveur : <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
|
||||
@@ -819,7 +811,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8680abbea249ebe9c2fe35556559c8e1a9eb5841">
|
||||
<source>Document processing</source>
|
||||
<target>Traitement de document</target>
|
||||
<target>Traitement de documents</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
@@ -1234,6 +1226,14 @@
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6523384805359286307">
|
||||
<source>Title: <x equiv-text="rule.value" id="PH"/></source>
|
||||
<target>Titre : <x equiv-text="rule.value" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="02d184c288f567825a1fcbf83bcd3099a10853d5">
|
||||
<source>Filter tags</source>
|
||||
<target>Filtrer les étiquettes</target>
|
||||
@@ -1392,7 +1392,7 @@
|
||||
<target>Une erreur s'est produite lors de l'exécution de l'opération de masse : <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7894972847287473517">
|
||||
@@ -1400,7 +1400,7 @@
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">113</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8639884465898458690">
|
||||
@@ -1408,7 +1408,7 @@
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>" et "<x equiv-text="items[1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">This is for messages like 'modify "tag1" and "tag2"'</note>
|
||||
</trans-unit>
|
||||
@@ -1417,7 +1417,7 @@
|
||||
<target>, </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is used to separate enumerations and should probably be a comma and a whitespace in most languages</note>
|
||||
</trans-unit>
|
||||
@@ -1426,7 +1426,7 @@
|
||||
<target><x equiv-text="list" id="PH"/> et "<x equiv-text="items[items.length - 1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note>
|
||||
</trans-unit>
|
||||
@@ -1435,7 +1435,7 @@
|
||||
<target>Confirmer l'affectation des étiquettes</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6619516195038467207">
|
||||
@@ -1443,7 +1443,7 @@
|
||||
<target>Cette action affectera l'étiquette "<x equiv-text="tag.name" id="PH"/>" au(x) <x equiv-text="this.list.selected.size" id="PH_1"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1894412783609570695">
|
||||
@@ -1451,7 +1451,7 @@
|
||||
<target>Cette action affectera les étiquettes <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> au(x) <x equiv-text="this.list.selected.size" id="PH_1"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7181166515756808573">
|
||||
@@ -1459,7 +1459,7 @@
|
||||
<target>Cette action supprimera l'étiquette "<x equiv-text="tag.name" id="PH"/>" de(s) <x equiv-text="this.list.selected.size" id="PH_1"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3819792277998068944">
|
||||
@@ -1467,7 +1467,7 @@
|
||||
<target>Cette action supprimera les étiquettes <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH"/> de(s) <x equiv-text="this.list.selected.size" id="PH_1"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
<context context-type="linenumber">137</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2739066218579571288">
|
||||
@@ -1475,7 +1475,7 @@
|
||||
<target>Cette action affectera les étiquettes <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> et supprimera les étiquettes <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH_1"/> de(s) <x equiv-text="this.list.selected.size" id="PH_2"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
<context context-type="linenumber">139</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2996713129519325161">
|
||||
@@ -1483,7 +1483,7 @@
|
||||
<target>Confirmer l'affectation du correspondant</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">158</context>
|
||||
<context context-type="linenumber">159</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6900893559485781849">
|
||||
@@ -1491,7 +1491,7 @@
|
||||
<target>Cette action affectera le correspondant "<x equiv-text="correspondent.name" id="PH"/>" au(x) <x equiv-text="this.list.selected.size" id="PH_1"/>document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">160</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1257522660364398440">
|
||||
@@ -1499,7 +1499,7 @@
|
||||
<target>Cette action supprimera le correspondant de(s) <x equiv-text="this.list.selected.size" id="PH"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5393409374423140648">
|
||||
@@ -1507,7 +1507,7 @@
|
||||
<target>Confirmer l'affectation du type de document</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="332180123895325027">
|
||||
@@ -1515,7 +1515,7 @@
|
||||
<target>Cette action affectera le type de document "<x equiv-text="documentType.name" id="PH"/>" au(x) <x equiv-text="this.list.selected.size" id="PH_1"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
<context context-type="linenumber">184</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2236642492594872779">
|
||||
@@ -1523,7 +1523,7 @@
|
||||
<target>Cette action supprimera le type de document de(s) <x equiv-text="this.list.selected.size" id="PH"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">185</context>
|
||||
<context context-type="linenumber">186</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="749430623564850405">
|
||||
@@ -1531,7 +1531,7 @@
|
||||
<target>Confirmer la suppression</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">200</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4303174930844518780">
|
||||
@@ -1539,7 +1539,7 @@
|
||||
<target>Cette action supprimera définitivement <x equiv-text="this.list.selected.size" id="PH"/> document(s) sélectionné(s).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5641451190833696892">
|
||||
@@ -1547,7 +1547,7 @@
|
||||
<target>Cette action est irréversible.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6734339521247847366">
|
||||
@@ -1555,7 +1555,7 @@
|
||||
<target>Supprimer le(s) document(s)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8b0609df23817024b3bed12beb9b64fc1009f588">
|
||||
@@ -1582,6 +1582,14 @@
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="fc2de37422d7c4af6686842283cc2afd781b6848">
|
||||
<source>Download originals</source>
|
||||
<target>Télécharger les originaux</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
|
||||
<source>Suggestions:</source>
|
||||
<target>Suggestions : </target>
|
||||
@@ -1614,22 +1622,22 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="46c8fe557cf52c9389783627d4f85453f4ddb459">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Documents dans la boîte de réception : <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="c327c0e67bcac7494dcbaa9afb3b42d5008c6438">
|
||||
<source>Total documents: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Nombre total de documents : <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<trans-unit datatype="html" id="c0d907c2687c09612395aee6ef7c04ca8e5e5e0a">
|
||||
<source>Total documents: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Nombre total de documents : <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="13e8d49dbcad9f9d71e66a9a56d6f328cff430c9">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Documents dans la boîte de réception : <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6443586946875325554">
|
||||
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
|
||||
<target>Traitement : <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
|
||||
@@ -1816,6 +1824,14 @@
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="d6529debfc1613db22d6fa096ebfeb8a85fa739d">
|
||||
<source>Invalid date.</source>
|
||||
<target>Date incorrecte.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2807800733729323332">
|
||||
<source>Yes</source>
|
||||
<target>Oui</target>
|
||||
@@ -1845,7 +1861,15 @@
|
||||
<target>Anglais (US)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6987083569809053351">
|
||||
<source>English (GB)</source>
|
||||
<target>Anglais (GB)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1858110241312746425">
|
||||
@@ -1853,7 +1877,7 @@
|
||||
<target>Allemand</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3071065188816255493">
|
||||
@@ -1861,7 +1885,7 @@
|
||||
<target>Néerlandais</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7633754075223722162">
|
||||
@@ -1869,7 +1893,23 @@
|
||||
<target>Français</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9184513005098760425">
|
||||
<source>Portuguese (Brazil)</source>
|
||||
<target>Portugais (Brésil)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4912706592792948707">
|
||||
<source>ISO 8601</source>
|
||||
<target>ISO 8601</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2119857572761283468">
|
||||
|
@@ -58,11 +58,11 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2155249406916744630">
|
||||
<source>View "<x equiv-text="this.list.savedView.name" id="PH"/>" saved successfully.</source>
|
||||
<target>View "<x equiv-text="this.list.savedView.name" id="PH"/>" met succes opgeslagen.</target>
|
||||
<source>View "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" saved successfully.</source>
|
||||
<target>View "<x equiv-text="this.list.activeSavedViewTitle" id="PH"/>" met succes opgeslagen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6837554170707123455">
|
||||
@@ -70,7 +70,7 @@
|
||||
<target>View "<x equiv-text="savedView.name" id="PH"/>" met succes gemaakt.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
|
||||
@@ -129,9 +129,9 @@
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="72e7d343f9165602cce1ca7faffbc565fd31ef92">
|
||||
<source>Save "<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>Opslaan "<x equiv-text="{{list.savedViewTitle}}" id="INTERPOLATION"/>"</target>
|
||||
<trans-unit datatype="html" id="5f5ce787c428d917c30c9bd70789a618e09743a7">
|
||||
<source>Save "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</source>
|
||||
<target>Opslaan "<x equiv-text="{{list.activeSavedViewTitle}}" id="INTERPOLATION"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
@@ -585,14 +585,6 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5ca707824ab93066c7d9b44e1b8bf216725c2c22">
|
||||
<source>Filter</source>
|
||||
<target>Filter</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5610279464668232148">
|
||||
<source>Saved view "<x equiv-text="savedView.name" id="PH"/>" deleted.</source>
|
||||
<target>Opgeslagen view "<x equiv-text="savedView.name" id="PH"/>" verwijderd.</target>
|
||||
@@ -622,7 +614,15 @@
|
||||
<target>Datumopmaak van weergavetaal gebruiken</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">95</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4912706592792948707">
|
||||
<source>ISO 8601</source>
|
||||
<target>ISO 8601</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">97</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8488620293789898901">
|
||||
@@ -630,7 +630,7 @@
|
||||
<target>Fout bij het opslaan van de instellingen: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
|
||||
@@ -1234,6 +1234,14 @@
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6523384805359286307">
|
||||
<source>Title: <x equiv-text="rule.value" id="PH"/></source>
|
||||
<target>Titel: <x equiv-text="rule.value" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="02d184c288f567825a1fcbf83bcd3099a10853d5">
|
||||
<source>Filter tags</source>
|
||||
<target>Etiketten filteren</target>
|
||||
@@ -1392,7 +1400,7 @@
|
||||
<target>Fout bij het uitvoeren van een massabewerking: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7894972847287473517">
|
||||
@@ -1400,7 +1408,7 @@
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">113</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8639884465898458690">
|
||||
@@ -1408,7 +1416,7 @@
|
||||
<target>"<x equiv-text="items[0].name" id="PH"/>" en "<x equiv-text="items[1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
<context context-type="linenumber">115</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">This is for messages like 'modify "tag1" and "tag2"'</note>
|
||||
</trans-unit>
|
||||
@@ -1417,7 +1425,7 @@
|
||||
<target>, </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is used to separate enumerations and should probably be a comma and a whitespace in most languages</note>
|
||||
</trans-unit>
|
||||
@@ -1426,7 +1434,7 @@
|
||||
<target><x equiv-text="list" id="PH"/> en "<x equiv-text="items[items.length - 1].name" id="PH_1"/>"</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
<note from="description" priority="1">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note>
|
||||
</trans-unit>
|
||||
@@ -1435,7 +1443,7 @@
|
||||
<target>Bevestig toewijzen van etiketten</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6619516195038467207">
|
||||
@@ -1443,7 +1451,7 @@
|
||||
<target>Het etiket "<x equiv-text="tag.name" id="PH"/>" zal aan <x equiv-text="this.list.selected.size" id="PH_1"/> geselecteerd(e) document(en) worden toegewezen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1894412783609570695">
|
||||
@@ -1451,7 +1459,7 @@
|
||||
<target>De etiketten <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> zullen aan <x equiv-text="this.list.selected.size" id="PH_1"/> geselecteerd(e) document(en) worden toegewezen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7181166515756808573">
|
||||
@@ -1459,7 +1467,7 @@
|
||||
<target>Het etiket "<x equiv-text="tag.name" id="PH"/>" zal verwijderd worden van <x equiv-text="this.list.selected.size" id="PH_1"/> geselecteerd(e) document(en).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3819792277998068944">
|
||||
@@ -1467,7 +1475,7 @@
|
||||
<target>De etiketten <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH"/> zullen verwijderd worden van <x equiv-text="this.list.selected.size" id="PH_1"/> geselecteerd(e) document(en).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
<context context-type="linenumber">137</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2739066218579571288">
|
||||
@@ -1475,7 +1483,7 @@
|
||||
<target>De etiketten <x equiv-text="this._localizeList(changedTags.itemsToAdd)" id="PH"/> zullen toegevoegd worden aan, en de etiketten <x equiv-text="this._localizeList(changedTags.itemsToRemove)" id="PH_1"/> zullen verwijderd worden van <x equiv-text="this.list.selected.size" id="PH_2"/> geselecteerd(e) document(en).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
<context context-type="linenumber">139</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2996713129519325161">
|
||||
@@ -1483,7 +1491,7 @@
|
||||
<target>Bevestig toewijzen van correspondent</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">158</context>
|
||||
<context context-type="linenumber">159</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6900893559485781849">
|
||||
@@ -1491,7 +1499,7 @@
|
||||
<target>De correspondent "<x equiv-text="correspondent.name" id="PH"/>" zal aan <x equiv-text="this.list.selected.size" id="PH_1"/> geselecteerd(e) document(en) worden toegewezen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">160</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1257522660364398440">
|
||||
@@ -1499,7 +1507,7 @@
|
||||
<target>De correspondent zal verwijderd worden van <x equiv-text="this.list.selected.size" id="PH"/> geselecteerd(e) document(en).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5393409374423140648">
|
||||
@@ -1507,7 +1515,7 @@
|
||||
<target>Bevestig toewijzen van documenttype</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="332180123895325027">
|
||||
@@ -1515,7 +1523,7 @@
|
||||
<target>Het documenttype "<x equiv-text="documentType.name" id="PH"/>" zal aan <x equiv-text="this.list.selected.size" id="PH_1"/> geselecteerd(e) document(en) worden toegewezen.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
<context context-type="linenumber">184</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2236642492594872779">
|
||||
@@ -1523,7 +1531,7 @@
|
||||
<target>Het documenttype zal verwijderd worden van <x equiv-text="this.list.selected.size" id="PH"/> geselecteerd(e) document(en).</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">185</context>
|
||||
<context context-type="linenumber">186</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="749430623564850405">
|
||||
@@ -1531,7 +1539,7 @@
|
||||
<target>Bevestig verwijderen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">200</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="4303174930844518780">
|
||||
@@ -1539,7 +1547,7 @@
|
||||
<target><x equiv-text="this.list.selected.size" id="PH"/> geselecteerd(e) document(en) zullen permanent worden verwijderd.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="5641451190833696892">
|
||||
@@ -1547,7 +1555,7 @@
|
||||
<target>Deze actie kan niet ongedaan worden gemaakt.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">202</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6734339521247847366">
|
||||
@@ -1555,7 +1563,7 @@
|
||||
<target>Verwijder document(en)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="8b0609df23817024b3bed12beb9b64fc1009f588">
|
||||
@@ -1582,6 +1590,14 @@
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="fc2de37422d7c4af6686842283cc2afd781b6848">
|
||||
<source>Download originals</source>
|
||||
<target>Originelen downloaden</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
|
||||
<source>Suggestions:</source>
|
||||
<target>Suggesties:</target>
|
||||
@@ -1614,22 +1630,22 @@
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="46c8fe557cf52c9389783627d4f85453f4ddb459">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Documenten in "Postvak in": <x equiv-text="{{statistics.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="c327c0e67bcac7494dcbaa9afb3b42d5008c6438">
|
||||
<source>Total documents: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Totaal aantal documenten: <x equiv-text="{{statistics.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<trans-unit datatype="html" id="c0d907c2687c09612395aee6ef7c04ca8e5e5e0a">
|
||||
<source>Total documents: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></source>
|
||||
<target>Totaal aantal documenten: <x equiv-text="{{statistics?.documents_total}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="13e8d49dbcad9f9d71e66a9a56d6f328cff430c9">
|
||||
<source>Documents in inbox: <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></source>
|
||||
<target>Documenten in "Postvak in": <x equiv-text="{{statistics?.documents_inbox}}" id="INTERPOLATION"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6443586946875325554">
|
||||
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
|
||||
<target>Bezig met verwerken: <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
|
||||
@@ -1848,12 +1864,20 @@
|
||||
<context context-type="linenumber">82</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="6987083569809053351">
|
||||
<source>English (GB)</source>
|
||||
<target>Engels (Brits)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="1858110241312746425">
|
||||
<source>German</source>
|
||||
<target>Duits</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="3071065188816255493">
|
||||
@@ -1861,7 +1885,7 @@
|
||||
<target>Nederlands</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="7633754075223722162">
|
||||
@@ -1869,7 +1893,7 @@
|
||||
<target>Frans</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit datatype="html" id="2119857572761283468">
|
||||
|
2353
src-ui/src/locale/messages.pt_BR.xlf
Normal file
2353
src-ui/src/locale/messages.pt_BR.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@@ -246,6 +246,16 @@ $border-color-dark-mode: #47494f;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-light:not(:disabled):not(.disabled) {
|
||||
background-color: $bg-dark-mode;
|
||||
color: $text-color-dark-mode-accent;
|
||||
|
||||
&:hover {
|
||||
background-color: $text-color-dark-mode;
|
||||
color: $bg-dark-mode;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-link:not(:disabled):not(.disabled) {
|
||||
color: $primary-dark-mode;
|
||||
}
|
||||
@@ -366,6 +376,12 @@ $border-color-dark-mode: #47494f;
|
||||
.progress-bar.bg-primary {
|
||||
background-color: darken($primary-dark-mode, 5%) !important;
|
||||
}
|
||||
|
||||
.ngb-dp-header,
|
||||
.ngb-dp-weekdays,
|
||||
.ngb-dp-month {
|
||||
background-color: $bg-light-dark-mode;
|
||||
}
|
||||
}
|
||||
|
||||
body.color-scheme-dark {
|
||||
|
60
src/documents/bulk_download.py
Normal file
60
src/documents/bulk_download.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from zipfile import ZipFile
|
||||
|
||||
from documents.models import Document
|
||||
|
||||
|
||||
class BulkArchiveStrategy:
|
||||
|
||||
def __init__(self, zipf: ZipFile):
|
||||
self.zipf = zipf
|
||||
|
||||
def make_unique_filename(self,
|
||||
doc: Document,
|
||||
archive: bool = False,
|
||||
folder: str = ""):
|
||||
counter = 0
|
||||
while True:
|
||||
filename = folder + doc.get_public_filename(archive, counter)
|
||||
if filename in self.zipf.namelist():
|
||||
counter += 1
|
||||
else:
|
||||
return filename
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
raise NotImplementedError() # pragma: no cover
|
||||
|
||||
|
||||
class OriginalsOnlyStrategy(BulkArchiveStrategy):
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
self.zipf.write(doc.source_path, self.make_unique_filename(doc))
|
||||
|
||||
|
||||
class ArchiveOnlyStrategy(BulkArchiveStrategy):
|
||||
|
||||
def __init__(self, zipf):
|
||||
super(ArchiveOnlyStrategy, self).__init__(zipf)
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
if doc.has_archive_version:
|
||||
self.zipf.write(doc.archive_path,
|
||||
self.make_unique_filename(doc, archive=True))
|
||||
else:
|
||||
self.zipf.write(doc.source_path,
|
||||
self.make_unique_filename(doc))
|
||||
|
||||
|
||||
class OriginalAndArchiveStrategy(BulkArchiveStrategy):
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
if doc.has_archive_version:
|
||||
self.zipf.write(
|
||||
doc.archive_path, self.make_unique_filename(
|
||||
doc, archive=True, folder="archive/"
|
||||
)
|
||||
)
|
||||
|
||||
self.zipf.write(
|
||||
doc.source_path,
|
||||
self.make_unique_filename(doc, folder="originals/")
|
||||
)
|
@@ -31,10 +31,24 @@ def handle_document(document_id):
|
||||
|
||||
parser_class = get_parser_class_for_mime_type(mime_type)
|
||||
|
||||
if not parser_class:
|
||||
logger.error(f"No parser found for mime type {mime_type}, cannot "
|
||||
f"archive document {document} (ID: {document_id})")
|
||||
return
|
||||
|
||||
parser = parser_class(logging_group=uuid.uuid4())
|
||||
|
||||
try:
|
||||
parser.parse(document.source_path, mime_type)
|
||||
parser.parse(
|
||||
document.source_path,
|
||||
mime_type,
|
||||
document.get_public_filename())
|
||||
|
||||
thumbnail = parser.get_optimised_thumbnail(
|
||||
document.source_path,
|
||||
mime_type,
|
||||
document.get_public_filename()
|
||||
)
|
||||
|
||||
if parser.get_archive_path():
|
||||
with transaction.atomic():
|
||||
@@ -55,12 +69,14 @@ def handle_document(document_id):
|
||||
create_source_path_directory(document.archive_path)
|
||||
shutil.move(parser.get_archive_path(),
|
||||
document.archive_path)
|
||||
shutil.move(thumbnail, document.thumbnail_path)
|
||||
|
||||
with AsyncWriter(index.open_index()) as writer:
|
||||
index.update_document(writer, document)
|
||||
with index.open_index_writer() as writer:
|
||||
index.update_document(writer, document)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error while parsing document {document}")
|
||||
logger.exception(f"Error while parsing document {document} "
|
||||
f"(ID: {document_id})")
|
||||
finally:
|
||||
parser.cleanup()
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from django.conf import settings
|
||||
@@ -57,6 +58,7 @@ def _consume(filepath):
|
||||
logger.exception("Error creating tags from path")
|
||||
|
||||
try:
|
||||
logger.info(f"Adding {filepath} to the task queue.")
|
||||
async_task("documents.tasks.consume_file",
|
||||
filepath,
|
||||
override_tag_ids=tag_ids if tag_ids else None,
|
||||
@@ -68,10 +70,11 @@ def _consume(filepath):
|
||||
logger.exception("Error while consuming document")
|
||||
|
||||
|
||||
def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
|
||||
def _consume_wait_unmodified(file):
|
||||
logger.debug(f"Waiting for file {file} to remain unmodified")
|
||||
mtime = -1
|
||||
current_try = 0
|
||||
while current_try < num_tries:
|
||||
while current_try < settings.CONSUMER_POLLING_RETRY_COUNT:
|
||||
try:
|
||||
new_mtime = os.stat(file).st_mtime
|
||||
except FileNotFoundError:
|
||||
@@ -82,7 +85,7 @@ def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
|
||||
_consume(file)
|
||||
return
|
||||
mtime = new_mtime
|
||||
sleep(wait_time)
|
||||
sleep(settings.CONSUMER_POLLING_DELAY)
|
||||
current_try += 1
|
||||
|
||||
logger.error(f"Timeout while waiting on file {file} to remain unmodified.")
|
||||
@@ -91,10 +94,14 @@ def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
|
||||
class Handler(FileSystemEventHandler):
|
||||
|
||||
def on_created(self, event):
|
||||
_consume_wait_unmodified(event.src_path)
|
||||
Thread(
|
||||
target=_consume_wait_unmodified, args=(event.src_path,)
|
||||
).start()
|
||||
|
||||
def on_moved(self, event):
|
||||
_consume_wait_unmodified(event.dest_path)
|
||||
Thread(
|
||||
target=_consume_wait_unmodified, args=(event.dest_path,)
|
||||
).start()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import re
|
||||
|
||||
import magic
|
||||
from django.utils.text import slugify
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
|
||||
from . import bulk_edit
|
||||
from .models import Correspondent, Tag, Document, Log, DocumentType, \
|
||||
SavedView, SavedViewFilterRule
|
||||
from .models import Correspondent, Tag, Document, DocumentType, \
|
||||
SavedView, SavedViewFilterRule, MatchingModel
|
||||
from .parsers import is_mime_type_supported
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
@@ -33,16 +35,30 @@ class DynamicFieldsModelSerializer(serializers.ModelSerializer):
|
||||
self.fields.pop(field_name)
|
||||
|
||||
|
||||
class CorrespondentSerializer(serializers.ModelSerializer):
|
||||
class MatchingModelSerializer(serializers.ModelSerializer):
|
||||
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
last_correspondence = serializers.DateTimeField(read_only=True)
|
||||
|
||||
def get_slug(self, obj):
|
||||
return slugify(obj.name)
|
||||
slug = SerializerMethodField()
|
||||
|
||||
def validate_match(self, match):
|
||||
if 'matching_algorithm' in self.initial_data and self.initial_data['matching_algorithm'] == MatchingModel.MATCH_REGEX: # NOQA: E501
|
||||
try:
|
||||
re.compile(match)
|
||||
except Exception as e:
|
||||
raise serializers.ValidationError(
|
||||
_("Invalid regular expresssion: %(error)s") %
|
||||
{'error': str(e)}
|
||||
)
|
||||
return match
|
||||
|
||||
|
||||
class CorrespondentSerializer(MatchingModelSerializer):
|
||||
|
||||
last_correspondence = serializers.DateTimeField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Correspondent
|
||||
fields = (
|
||||
@@ -57,13 +73,7 @@ class CorrespondentSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class DocumentTypeSerializer(serializers.ModelSerializer):
|
||||
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
def get_slug(self, obj):
|
||||
return slugify(obj.name)
|
||||
slug = SerializerMethodField()
|
||||
class DocumentTypeSerializer(MatchingModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = DocumentType
|
||||
@@ -78,13 +88,7 @@ class DocumentTypeSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class TagSerializer(serializers.ModelSerializer):
|
||||
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
def get_slug(self, obj):
|
||||
return slugify(obj.name)
|
||||
slug = SerializerMethodField()
|
||||
class TagSerializer(MatchingModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
@@ -192,14 +196,34 @@ class SavedViewSerializer(serializers.ModelSerializer):
|
||||
return saved_view
|
||||
|
||||
|
||||
class BulkEditSerializer(serializers.Serializer):
|
||||
class DocumentListSerializer(serializers.Serializer):
|
||||
|
||||
documents = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
required=True,
|
||||
label="Documents",
|
||||
write_only=True
|
||||
write_only=True,
|
||||
child=serializers.IntegerField()
|
||||
)
|
||||
|
||||
def _validate_document_id_list(self, documents, name="documents"):
|
||||
if not type(documents) == list:
|
||||
raise serializers.ValidationError(f"{name} must be a list")
|
||||
if not all([type(i) == int for i in documents]):
|
||||
raise serializers.ValidationError(
|
||||
f"{name} must be a list of integers")
|
||||
count = Document.objects.filter(id__in=documents).count()
|
||||
if not count == len(documents):
|
||||
raise serializers.ValidationError(
|
||||
f"Some documents in {name} don't exist or were "
|
||||
f"specified twice.")
|
||||
|
||||
def validate_documents(self, documents):
|
||||
self._validate_document_id_list(documents)
|
||||
return documents
|
||||
|
||||
|
||||
class BulkEditSerializer(DocumentListSerializer):
|
||||
|
||||
method = serializers.ChoiceField(
|
||||
choices=[
|
||||
"set_correspondent",
|
||||
@@ -215,18 +239,6 @@ class BulkEditSerializer(serializers.Serializer):
|
||||
|
||||
parameters = serializers.DictField(allow_empty=True)
|
||||
|
||||
def _validate_document_id_list(self, documents, name="documents"):
|
||||
if not type(documents) == list:
|
||||
raise serializers.ValidationError(f"{name} must be a list")
|
||||
if not all([type(i) == int for i in documents]):
|
||||
raise serializers.ValidationError(
|
||||
f"{name} must be a list of integers")
|
||||
count = Document.objects.filter(id__in=documents).count()
|
||||
if not count == len(documents):
|
||||
raise serializers.ValidationError(
|
||||
f"Some documents in {name} don't exist or were "
|
||||
f"specified twice.")
|
||||
|
||||
def _validate_tag_id_list(self, tags, name="tags"):
|
||||
if not type(tags) == list:
|
||||
raise serializers.ValidationError(f"{name} must be a list")
|
||||
@@ -238,10 +250,6 @@ class BulkEditSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError(
|
||||
f"Some tags in {name} don't exist or were specified twice.")
|
||||
|
||||
def validate_documents(self, documents):
|
||||
self._validate_document_id_list(documents)
|
||||
return documents
|
||||
|
||||
def validate_method(self, method):
|
||||
if method == "set_correspondent":
|
||||
return bulk_edit.set_correspondent
|
||||
@@ -392,9 +400,24 @@ class PostDocumentSerializer(serializers.Serializer):
|
||||
return None
|
||||
|
||||
|
||||
class SelectionDataSerializer(serializers.Serializer):
|
||||
class BulkDownloadSerializer(DocumentListSerializer):
|
||||
|
||||
documents = serializers.ListField(
|
||||
required=True,
|
||||
child=serializers.IntegerField()
|
||||
content = serializers.ChoiceField(
|
||||
choices=["archive", "originals", "both"],
|
||||
default="archive"
|
||||
)
|
||||
|
||||
compression = serializers.ChoiceField(
|
||||
choices=["none", "deflated", "bzip2", "lzma"],
|
||||
default="none"
|
||||
)
|
||||
|
||||
def validate_compression(self, compression):
|
||||
import zipfile
|
||||
|
||||
return {
|
||||
"none": zipfile.ZIP_STORED,
|
||||
"deflated": zipfile.ZIP_DEFLATED,
|
||||
"bzip2": zipfile.ZIP_BZIP2,
|
||||
"lzma": zipfile.ZIP_LZMA
|
||||
}[compression]
|
||||
|
@@ -15,6 +15,7 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="manifest" href="{% static webmanifest %}">
|
||||
<link rel="stylesheet" href="{% static styles_css %}">
|
||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||
</head>
|
||||
<body>
|
||||
<app-root>{% translate "Paperless-ng is loading..." %}</app-root>
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
@@ -11,7 +14,7 @@ from rest_framework.test import APITestCase
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
from documents import index, bulk_edit
|
||||
from documents.models import Document, Correspondent, DocumentType, Tag, SavedView
|
||||
from documents.models import Document, Correspondent, DocumentType, Tag, SavedView, MatchingModel
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
@@ -769,6 +772,41 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertListEqual(response.data, ["test", "test2"])
|
||||
|
||||
def test_invalid_regex_other_algorithm(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"matching_algorithm": MatchingModel.MATCH_ANY,
|
||||
"match": "["
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 201, endpoint)
|
||||
|
||||
def test_invalid_regex(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"matching_algorithm": MatchingModel.MATCH_REGEX,
|
||||
"match": "["
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 400, endpoint)
|
||||
|
||||
def test_valid_regex(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"matching_algorithm": MatchingModel.MATCH_REGEX,
|
||||
"match": "[0-9]"
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 201, endpoint)
|
||||
|
||||
def test_regex_no_algorithm(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"match": "[0-9]"
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 201, endpoint)
|
||||
|
||||
|
||||
class TestBulkEdit(DirectoriesMixin, APITestCase):
|
||||
|
||||
@@ -1123,6 +1161,113 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
|
||||
self.assertCountEqual(response.data['selected_document_types'], [{"id": self.c1.id, "document_count": 1}, {"id": self.c2.id, "document_count": 0}])
|
||||
|
||||
|
||||
class TestBulkDownload(DirectoriesMixin, APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBulkDownload, self).setUp()
|
||||
|
||||
user = User.objects.create_superuser(username="temp_admin")
|
||||
self.client.force_login(user=user)
|
||||
|
||||
self.doc1 = Document.objects.create(title="unrelated", checksum="A")
|
||||
self.doc2 = Document.objects.create(title="document A", filename="docA.pdf", mime_type="application/pdf", checksum="B", created=datetime.datetime(2021, 1, 1))
|
||||
self.doc2b = Document.objects.create(title="document A", filename="docA2.pdf", mime_type="application/pdf", checksum="D", created=datetime.datetime(2021, 1, 1))
|
||||
self.doc3 = Document.objects.create(title="document B", filename="docB.jpg", mime_type="image/jpeg", checksum="C", created=datetime.datetime(2020, 3, 21), archive_filename="docB.pdf", archive_checksum="D")
|
||||
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), self.doc2.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.png"), self.doc2b.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.jpg"), self.doc3.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "test_with_bom.pdf"), self.doc3.archive_path)
|
||||
|
||||
def test_download_originals(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"content": "originals"
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 2)
|
||||
self.assertIn("2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("2020-03-21 document B.jpg", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc3.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2020-03-21 document B.jpg"))
|
||||
|
||||
def test_download_default(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc3.id]
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 2)
|
||||
self.assertIn("2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("2020-03-21 document B.pdf", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc3.archive_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2020-03-21 document B.pdf"))
|
||||
|
||||
def test_download_both(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"content": "both"
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 3)
|
||||
self.assertIn("originals/2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("archive/2020-03-21 document B.pdf", zipf.namelist())
|
||||
self.assertIn("originals/2020-03-21 document B.jpg", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("originals/2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc3.archive_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("archive/2020-03-21 document B.pdf"))
|
||||
|
||||
with self.doc3.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("originals/2020-03-21 document B.jpg"))
|
||||
|
||||
def test_filename_clashes(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc2b.id]
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 2)
|
||||
|
||||
self.assertIn("2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("2021-01-01 document A_01.pdf", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc2b.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A_01.pdf"))
|
||||
|
||||
def test_compression(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc2b.id],
|
||||
"compression": "lzma"
|
||||
}), content_type='application/json')
|
||||
|
||||
class TestApiAuth(APITestCase):
|
||||
|
||||
def test_auth_required(self):
|
||||
@@ -1146,4 +1291,5 @@ class TestApiAuth(APITestCase):
|
||||
self.assertEqual(self.client.get("/api/search/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/search/auto_complete/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/documents/bulk_edit/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/documents/bulk_download/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/documents/selection_data/").status_code, 401)
|
||||
|
@@ -49,6 +49,21 @@ class TestArchiver(DirectoriesMixin, TestCase):
|
||||
self.assertTrue(filecmp.cmp(sample_file, doc.source_path))
|
||||
self.assertEqual(doc.archive_filename, "none/A.pdf")
|
||||
|
||||
def test_unknown_mime_type(self):
|
||||
doc = self.make_models()
|
||||
doc.mime_type = "sdgfh"
|
||||
doc.save()
|
||||
shutil.copy(sample_file, doc.source_path)
|
||||
|
||||
handle_document(doc.pk)
|
||||
|
||||
doc = Document.objects.get(id=doc.id)
|
||||
|
||||
self.assertIsNotNone(doc.checksum)
|
||||
self.assertIsNone(doc.archive_checksum)
|
||||
self.assertIsNone(doc.archive_filename)
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
def test_naming_priorities(self):
|
||||
doc1 = Document.objects.create(checksum="A", title="document", content="first document", mime_type="application/pdf", filename="document.pdf")
|
||||
|
@@ -203,7 +203,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
|
||||
self.assertRaises(CommandError, call_command, 'document_consumer', '--oneshot')
|
||||
|
||||
|
||||
@override_settings(CONSUMER_POLLING=1)
|
||||
@override_settings(CONSUMER_POLLING=1, CONSUMER_POLLING_DELAY=1, CONSUMER_POLLING_RETRY_COUNT=20)
|
||||
class TestConsumerPolling(TestConsumer):
|
||||
# just do all the tests with polling
|
||||
pass
|
||||
@@ -215,8 +215,7 @@ class TestConsumerRecursive(TestConsumer):
|
||||
pass
|
||||
|
||||
|
||||
@override_settings(CONSUMER_RECURSIVE=True)
|
||||
@override_settings(CONSUMER_POLLING=1)
|
||||
@override_settings(CONSUMER_RECURSIVE=True, CONSUMER_POLLING=1, CONSUMER_POLLING_DELAY=1, CONSUMER_POLLING_RETRY_COUNT=20)
|
||||
class TestConsumerRecursivePolling(TestConsumer):
|
||||
# just do all the tests with polling and recursive
|
||||
pass
|
||||
@@ -257,6 +256,6 @@ class TestConsumerTags(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
|
||||
# their order.
|
||||
self.assertCountEqual(kwargs["override_tag_ids"], tag_ids)
|
||||
|
||||
@override_settings(CONSUMER_POLLING=1)
|
||||
@override_settings(CONSUMER_POLLING=1, CONSUMER_POLLING_DELAY=1, CONSUMER_POLLING_RETRY_COUNT=20)
|
||||
def test_consume_file_with_path_tags_polling(self):
|
||||
self.test_consume_file_with_path_tags()
|
||||
|
@@ -2,6 +2,7 @@ import logging
|
||||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
|
||||
@@ -34,6 +35,8 @@ from rest_framework.viewsets import (
|
||||
|
||||
from paperless.db import GnuPG
|
||||
from paperless.views import StandardPagination
|
||||
from .bulk_download import OriginalAndArchiveStrategy, OriginalsOnlyStrategy, \
|
||||
ArchiveOnlyStrategy
|
||||
from .classifier import load_classifier
|
||||
from .filters import (
|
||||
CorrespondentFilterSet,
|
||||
@@ -51,7 +54,9 @@ from .serialisers import (
|
||||
DocumentTypeSerializer,
|
||||
PostDocumentSerializer,
|
||||
SavedViewSerializer,
|
||||
BulkEditSerializer, SelectionDataSerializer
|
||||
BulkEditSerializer,
|
||||
DocumentListSerializer,
|
||||
BulkDownloadSerializer
|
||||
)
|
||||
|
||||
|
||||
@@ -444,7 +449,7 @@ class PostDocumentView(APIView):
|
||||
class SelectionDataView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = SelectionDataSerializer
|
||||
serializer_class = DocumentListSerializer
|
||||
parser_classes = (parsers.MultiPartParser, parsers.JSONParser)
|
||||
|
||||
def get_serializer_context(self):
|
||||
@@ -606,3 +611,55 @@ class StatisticsView(APIView):
|
||||
'documents_total': documents_total,
|
||||
'documents_inbox': documents_inbox,
|
||||
})
|
||||
|
||||
|
||||
class BulkDownloadView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = BulkDownloadSerializer
|
||||
parser_classes = (parsers.JSONParser,)
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
'request': self.request,
|
||||
'format': self.format_kwarg,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
def post(self, request, format=None):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
ids = serializer.validated_data.get('documents')
|
||||
compression = serializer.validated_data.get('compression')
|
||||
content = serializer.validated_data.get('content')
|
||||
|
||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||
temp = tempfile.NamedTemporaryFile(
|
||||
dir=settings.SCRATCH_DIR,
|
||||
suffix="-compressed-archive",
|
||||
delete=False)
|
||||
|
||||
if content == 'both':
|
||||
strategy_class = OriginalAndArchiveStrategy
|
||||
elif content == 'originals':
|
||||
strategy_class = OriginalsOnlyStrategy
|
||||
else:
|
||||
strategy_class = ArchiveOnlyStrategy
|
||||
|
||||
with zipfile.ZipFile(temp.name, "w", compression) as zipf:
|
||||
strategy = strategy_class(zipf)
|
||||
for id in ids:
|
||||
doc = Document.objects.get(id=id)
|
||||
strategy.add_document(doc)
|
||||
|
||||
with open(temp.name, "rb") as f:
|
||||
response = HttpResponse(f, content_type="application/zip")
|
||||
response["Content-Disposition"] = '{}; filename="{}"'.format(
|
||||
"attachment", "documents.zip")
|
||||
|
||||
return response
|
||||
|
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
|
||||
"POT-Creation-Date: 2021-02-24 16:49+0100\n"
|
||||
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
|
||||
"Last-Translator: Jonas Winkler, 2021\n"
|
||||
"Language-Team: German (https://www.transifex.com/paperless/teams/115905/de/)\n"
|
||||
@@ -354,7 +354,12 @@ msgstr "Filterregel"
|
||||
msgid "filter rules"
|
||||
msgstr "Filterregeln"
|
||||
|
||||
#: documents/serialisers.py:370
|
||||
#: documents/serialisers.py:52
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr "Ungültiger regulärer Ausdruck: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:378
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Dateityp %(type)s nicht unterstützt"
|
||||
@@ -421,7 +426,11 @@ msgstr "Niederländisch"
|
||||
msgid "French"
|
||||
msgstr "Französisch"
|
||||
|
||||
#: paperless/urls.py:114
|
||||
#: paperless/settings.py:296
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugiesisch (Brasilien)"
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng Administration"
|
||||
|
||||
|
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
|
||||
"POT-Creation-Date: 2021-02-24 16:49+0100\n"
|
||||
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
|
||||
"Last-Translator: Jonas Winkler, 2021\n"
|
||||
"Language-Team: English (United Kingdom) (https://www.transifex.com/paperless/teams/115905/en_GB/)\n"
|
||||
@@ -355,7 +355,12 @@ msgstr "filter rule"
|
||||
msgid "filter rules"
|
||||
msgstr "filter rules"
|
||||
|
||||
#: documents/serialisers.py:370
|
||||
#: documents/serialisers.py:52
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr "Invalid regular expresssion: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:378
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "File type %(type)s not supported"
|
||||
@@ -420,7 +425,11 @@ msgstr "Dutch"
|
||||
msgid "French"
|
||||
msgstr "French"
|
||||
|
||||
#: paperless/urls.py:114
|
||||
#: paperless/settings.py:296
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portuguese (Brazil)"
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng administration"
|
||||
|
||||
|
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
|
||||
"POT-Creation-Date: 2021-02-24 16:49+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -346,7 +346,12 @@ msgstr ""
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:370
|
||||
#: documents/serialisers.py:52
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:378
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
@@ -411,7 +416,11 @@ msgstr ""
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:114
|
||||
#: paperless/settings.py:296
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr ""
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Jonas Winkler, 2020
|
||||
# Jonas Winkler, 2021
|
||||
# Philmo67, 2021
|
||||
#
|
||||
#, fuzzy
|
||||
@@ -12,8 +12,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-28 22:02+0100\n"
|
||||
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
|
||||
"POT-Creation-Date: 2021-02-24 16:49+0100\n"
|
||||
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
|
||||
"Last-Translator: Philmo67, 2021\n"
|
||||
"Language-Team: French (https://www.transifex.com/paperless/teams/115905/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -26,64 +26,64 @@ msgstr ""
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
|
||||
#: documents/models.py:33
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Un des mots"
|
||||
|
||||
#: documents/models.py:34
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Tous les mots"
|
||||
|
||||
#: documents/models.py:35
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Concordance exacte"
|
||||
|
||||
#: documents/models.py:36
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expression régulière"
|
||||
|
||||
#: documents/models.py:37
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Mot approximatif"
|
||||
|
||||
#: documents/models.py:38
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatique"
|
||||
|
||||
#: documents/models.py:42 documents/models.py:352 paperless_mail/models.py:25
|
||||
#: documents/models.py:41 documents/models.py:364 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: documents/models.py:46
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "rapprochement"
|
||||
|
||||
#: documents/models.py:50
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algorithme de rapprochement"
|
||||
|
||||
#: documents/models.py:56
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "est insensible à la casse"
|
||||
|
||||
#: documents/models.py:75 documents/models.py:135
|
||||
#: documents/models.py:74 documents/models.py:134
|
||||
msgid "correspondent"
|
||||
msgstr "correspondant"
|
||||
|
||||
#: documents/models.py:76
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondants"
|
||||
|
||||
#: documents/models.py:98
|
||||
#: documents/models.py:97
|
||||
msgid "color"
|
||||
msgstr "couleur"
|
||||
|
||||
#: documents/models.py:102
|
||||
#: documents/models.py:101
|
||||
msgid "is inbox tag"
|
||||
msgstr "est une étiquette de boîte de réception"
|
||||
|
||||
#: documents/models.py:104
|
||||
#: documents/models.py:103
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
@@ -91,39 +91,39 @@ msgstr ""
|
||||
"Marque cette étiquette comme étiquette de boîte de réception : ces "
|
||||
"étiquettes sont affectées à tous les documents nouvellement traités."
|
||||
|
||||
#: documents/models.py:109
|
||||
#: documents/models.py:108
|
||||
msgid "tag"
|
||||
msgstr "étiquette"
|
||||
|
||||
#: documents/models.py:110 documents/models.py:166
|
||||
#: documents/models.py:109 documents/models.py:165
|
||||
msgid "tags"
|
||||
msgstr "étiquettes"
|
||||
|
||||
#: documents/models.py:116 documents/models.py:148
|
||||
#: documents/models.py:115 documents/models.py:147
|
||||
msgid "document type"
|
||||
msgstr "type de document"
|
||||
|
||||
#: documents/models.py:117
|
||||
#: documents/models.py:116
|
||||
msgid "document types"
|
||||
msgstr "types de document"
|
||||
|
||||
#: documents/models.py:125
|
||||
#: documents/models.py:124
|
||||
msgid "Unencrypted"
|
||||
msgstr "Non chiffré"
|
||||
|
||||
#: documents/models.py:126
|
||||
#: documents/models.py:125
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Chiffré avec GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:139
|
||||
#: documents/models.py:138
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: documents/models.py:152
|
||||
#: documents/models.py:151
|
||||
msgid "content"
|
||||
msgstr "contenu"
|
||||
|
||||
#: documents/models.py:154
|
||||
#: documents/models.py:153
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
@@ -131,43 +131,43 @@ msgstr ""
|
||||
"Les données brutes du document, en format texte uniquement. Ce champ est "
|
||||
"principalement utilisé pour la recherche."
|
||||
|
||||
#: documents/models.py:159
|
||||
#: documents/models.py:158
|
||||
msgid "mime type"
|
||||
msgstr "type mime"
|
||||
|
||||
#: documents/models.py:170
|
||||
#: documents/models.py:169
|
||||
msgid "checksum"
|
||||
msgstr "somme de contrôle"
|
||||
|
||||
#: documents/models.py:174
|
||||
#: documents/models.py:173
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "La somme de contrôle du document original."
|
||||
|
||||
#: documents/models.py:178
|
||||
#: documents/models.py:177
|
||||
msgid "archive checksum"
|
||||
msgstr "somme de contrôle de l'archive"
|
||||
|
||||
#: documents/models.py:183
|
||||
#: documents/models.py:182
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "La somme de contrôle du document archivé."
|
||||
|
||||
#: documents/models.py:187 documents/models.py:330
|
||||
#: documents/models.py:186 documents/models.py:342
|
||||
msgid "created"
|
||||
msgstr "créé le"
|
||||
|
||||
#: documents/models.py:191
|
||||
#: documents/models.py:190
|
||||
msgid "modified"
|
||||
msgstr "modifié"
|
||||
|
||||
#: documents/models.py:195
|
||||
#: documents/models.py:194
|
||||
msgid "storage type"
|
||||
msgstr "forme d'enregistrement :"
|
||||
|
||||
#: documents/models.py:203
|
||||
#: documents/models.py:202
|
||||
msgid "added"
|
||||
msgstr "date d'ajout"
|
||||
|
||||
#: documents/models.py:207
|
||||
#: documents/models.py:206
|
||||
msgid "filename"
|
||||
msgstr "nom du fichier"
|
||||
|
||||
@@ -176,179 +176,192 @@ msgid "Current filename in storage"
|
||||
msgstr "Nom du fichier courant en base de données"
|
||||
|
||||
#: documents/models.py:216
|
||||
msgid "archive filename"
|
||||
msgstr "nom de fichier de l'archive"
|
||||
|
||||
#: documents/models.py:222
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nom du fichier d'archive courant en base de données"
|
||||
|
||||
#: documents/models.py:226
|
||||
msgid "archive serial number"
|
||||
msgstr "numéro de série de l'archive"
|
||||
|
||||
#: documents/models.py:221
|
||||
#: documents/models.py:231
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
"Le classement de ce document dans votre archive de documents physiques."
|
||||
|
||||
#: documents/models.py:227
|
||||
#: documents/models.py:237
|
||||
msgid "document"
|
||||
msgstr "document"
|
||||
|
||||
#: documents/models.py:228
|
||||
#: documents/models.py:238
|
||||
msgid "documents"
|
||||
msgstr "documents"
|
||||
|
||||
#: documents/models.py:313
|
||||
#: documents/models.py:325
|
||||
msgid "debug"
|
||||
msgstr "débogage"
|
||||
|
||||
#: documents/models.py:314
|
||||
#: documents/models.py:326
|
||||
msgid "information"
|
||||
msgstr "information"
|
||||
|
||||
#: documents/models.py:315
|
||||
#: documents/models.py:327
|
||||
msgid "warning"
|
||||
msgstr "avertissement"
|
||||
|
||||
#: documents/models.py:316
|
||||
#: documents/models.py:328
|
||||
msgid "error"
|
||||
msgstr "erreur"
|
||||
|
||||
#: documents/models.py:317
|
||||
#: documents/models.py:329
|
||||
msgid "critical"
|
||||
msgstr "critique"
|
||||
|
||||
#: documents/models.py:321
|
||||
#: documents/models.py:333
|
||||
msgid "group"
|
||||
msgstr "groupe"
|
||||
|
||||
#: documents/models.py:324
|
||||
#: documents/models.py:336
|
||||
msgid "message"
|
||||
msgstr "message"
|
||||
|
||||
#: documents/models.py:327
|
||||
#: documents/models.py:339
|
||||
msgid "level"
|
||||
msgstr "niveau"
|
||||
|
||||
#: documents/models.py:334
|
||||
#: documents/models.py:346
|
||||
msgid "log"
|
||||
msgstr "rapport"
|
||||
|
||||
#: documents/models.py:335
|
||||
#: documents/models.py:347
|
||||
msgid "logs"
|
||||
msgstr "rapports"
|
||||
|
||||
#: documents/models.py:346 documents/models.py:396
|
||||
#: documents/models.py:358 documents/models.py:408
|
||||
msgid "saved view"
|
||||
msgstr "vue enregistrée"
|
||||
|
||||
#: documents/models.py:347
|
||||
#: documents/models.py:359
|
||||
msgid "saved views"
|
||||
msgstr "vues enregistrées"
|
||||
|
||||
#: documents/models.py:350
|
||||
#: documents/models.py:362
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: documents/models.py:356
|
||||
#: documents/models.py:368
|
||||
msgid "show on dashboard"
|
||||
msgstr "montrer sur le tableau de bord"
|
||||
|
||||
#: documents/models.py:359
|
||||
#: documents/models.py:371
|
||||
msgid "show in sidebar"
|
||||
msgstr "montrer dans la barre latérale"
|
||||
|
||||
#: documents/models.py:363
|
||||
#: documents/models.py:375
|
||||
msgid "sort field"
|
||||
msgstr "champ de tri"
|
||||
|
||||
#: documents/models.py:366
|
||||
#: documents/models.py:378
|
||||
msgid "sort reverse"
|
||||
msgstr "tri inverse"
|
||||
|
||||
#: documents/models.py:372
|
||||
#: documents/models.py:384
|
||||
msgid "title contains"
|
||||
msgstr "le titre contient"
|
||||
|
||||
#: documents/models.py:373
|
||||
#: documents/models.py:385
|
||||
msgid "content contains"
|
||||
msgstr "le contenu contient"
|
||||
|
||||
#: documents/models.py:374
|
||||
#: documents/models.py:386
|
||||
msgid "ASN is"
|
||||
msgstr "le NSA est"
|
||||
|
||||
#: documents/models.py:375
|
||||
#: documents/models.py:387
|
||||
msgid "correspondent is"
|
||||
msgstr "le correspondant est"
|
||||
|
||||
#: documents/models.py:376
|
||||
#: documents/models.py:388
|
||||
msgid "document type is"
|
||||
msgstr "le type de document est"
|
||||
|
||||
#: documents/models.py:377
|
||||
#: documents/models.py:389
|
||||
msgid "is in inbox"
|
||||
msgstr "est dans la boîte de réception"
|
||||
|
||||
#: documents/models.py:378
|
||||
#: documents/models.py:390
|
||||
msgid "has tag"
|
||||
msgstr "porte l'étiquette"
|
||||
|
||||
#: documents/models.py:379
|
||||
#: documents/models.py:391
|
||||
msgid "has any tag"
|
||||
msgstr "porte l'une des étiquettes"
|
||||
|
||||
#: documents/models.py:380
|
||||
#: documents/models.py:392
|
||||
msgid "created before"
|
||||
msgstr "créé avant"
|
||||
|
||||
#: documents/models.py:381
|
||||
#: documents/models.py:393
|
||||
msgid "created after"
|
||||
msgstr "créé après"
|
||||
|
||||
#: documents/models.py:382
|
||||
#: documents/models.py:394
|
||||
msgid "created year is"
|
||||
msgstr "l'année de création est"
|
||||
|
||||
#: documents/models.py:383
|
||||
#: documents/models.py:395
|
||||
msgid "created month is"
|
||||
msgstr "le mois de création est"
|
||||
|
||||
#: documents/models.py:384
|
||||
#: documents/models.py:396
|
||||
msgid "created day is"
|
||||
msgstr "le jour de création est"
|
||||
|
||||
#: documents/models.py:385
|
||||
#: documents/models.py:397
|
||||
msgid "added before"
|
||||
msgstr "ajouté avant"
|
||||
|
||||
#: documents/models.py:386
|
||||
#: documents/models.py:398
|
||||
msgid "added after"
|
||||
msgstr "ajouté après"
|
||||
|
||||
#: documents/models.py:387
|
||||
#: documents/models.py:399
|
||||
msgid "modified before"
|
||||
msgstr "modifié avant"
|
||||
|
||||
#: documents/models.py:388
|
||||
#: documents/models.py:400
|
||||
msgid "modified after"
|
||||
msgstr "modifié après"
|
||||
|
||||
#: documents/models.py:389
|
||||
#: documents/models.py:401
|
||||
msgid "does not have tag"
|
||||
msgstr "ne porte pas d'étiquette"
|
||||
|
||||
#: documents/models.py:400
|
||||
#: documents/models.py:412
|
||||
msgid "rule type"
|
||||
msgstr "type de règle"
|
||||
|
||||
#: documents/models.py:404
|
||||
#: documents/models.py:416
|
||||
msgid "value"
|
||||
msgstr "valeur"
|
||||
|
||||
#: documents/models.py:410
|
||||
#: documents/models.py:422
|
||||
msgid "filter rule"
|
||||
msgstr "règle de filtrage"
|
||||
|
||||
#: documents/models.py:411
|
||||
#: documents/models.py:423
|
||||
msgid "filter rules"
|
||||
msgstr "règles de filtrage"
|
||||
|
||||
#: documents/serialisers.py:383
|
||||
#: documents/serialisers.py:52
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr "Expression régulière incorrecte : %(error)s"
|
||||
|
||||
#: documents/serialisers.py:378
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Type de fichier %(type)s non pris en charge"
|
||||
@@ -395,23 +408,31 @@ msgstr "Mot de passe"
|
||||
msgid "Sign in"
|
||||
msgstr "S'identifier"
|
||||
|
||||
#: paperless/settings.py:286
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
#: paperless/settings.py:291
|
||||
msgid "English (US)"
|
||||
msgstr "Anglais (US)"
|
||||
|
||||
#: paperless/settings.py:287
|
||||
#: paperless/settings.py:292
|
||||
msgid "English (GB)"
|
||||
msgstr "Anglais (GB)"
|
||||
|
||||
#: paperless/settings.py:293
|
||||
msgid "German"
|
||||
msgstr "Allemand"
|
||||
|
||||
#: paperless/settings.py:288
|
||||
#: paperless/settings.py:294
|
||||
msgid "Dutch"
|
||||
msgstr "Néerlandais"
|
||||
|
||||
#: paperless/settings.py:289
|
||||
#: paperless/settings.py:295
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: paperless/urls.py:114
|
||||
#: paperless/settings.py:296
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugais (Brésil)"
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administration de Paperless-ng"
|
||||
|
||||
|
672
src/locale/pt_BR/LC_MESSAGES/django.po
Normal file
672
src/locale/pt_BR/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,672 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Jonas Winkler, 2021
|
||||
# Rodrigo A <rodrigo.avelino@meliuz.com.br>, 2021
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-24 16:49+0100\n"
|
||||
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
|
||||
"Last-Translator: Rodrigo A <rodrigo.avelino@meliuz.com.br>, 2021\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/paperless/teams/115905/pt_BR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pt_BR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Qualquer palavra"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Todas as palavras"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Detecção exata"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expressão regular"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Palavra difusa (fuzzy)"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:364 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "name"
|
||||
msgstr "nome"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "detecção"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algoritmo de detecção"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "diferencia maiúsculas de minúsculas"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:134
|
||||
msgid "correspondent"
|
||||
msgstr "correspondente"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondentes"
|
||||
|
||||
#: documents/models.py:97
|
||||
msgid "color"
|
||||
msgstr "cor"
|
||||
|
||||
#: documents/models.py:101
|
||||
msgid "is inbox tag"
|
||||
msgstr "é etiqueta caixa de entrada"
|
||||
|
||||
#: documents/models.py:103
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
msgstr ""
|
||||
"Marca essa etiqueta como caixa de entrada: Todos os novos documentos "
|
||||
"consumidos terão as etiquetas de caixa de entrada."
|
||||
|
||||
#: documents/models.py:108
|
||||
msgid "tag"
|
||||
msgstr "etiqueta"
|
||||
|
||||
#: documents/models.py:109 documents/models.py:165
|
||||
msgid "tags"
|
||||
msgstr "etiquetas"
|
||||
|
||||
#: documents/models.py:115 documents/models.py:147
|
||||
msgid "document type"
|
||||
msgstr "tipo de documento"
|
||||
|
||||
#: documents/models.py:116
|
||||
msgid "document types"
|
||||
msgstr "tipos de documento"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "Unencrypted"
|
||||
msgstr "Não encriptado"
|
||||
|
||||
#: documents/models.py:125
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Encriptado com GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:138
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: documents/models.py:151
|
||||
msgid "content"
|
||||
msgstr "conteúdo"
|
||||
|
||||
#: documents/models.py:153
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
msgstr ""
|
||||
"O conteúdo de texto bruto do documento. Esse campo é usado principalmente "
|
||||
"para busca."
|
||||
|
||||
#: documents/models.py:158
|
||||
msgid "mime type"
|
||||
msgstr "tipo mime"
|
||||
|
||||
#: documents/models.py:169
|
||||
msgid "checksum"
|
||||
msgstr "some de verificação"
|
||||
|
||||
#: documents/models.py:173
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "A soma de verificação original do documento."
|
||||
|
||||
#: documents/models.py:177
|
||||
msgid "archive checksum"
|
||||
msgstr "Soma de verificação de arquivamento."
|
||||
|
||||
#: documents/models.py:182
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "A soma de verificação do documento arquivado."
|
||||
|
||||
#: documents/models.py:186 documents/models.py:342
|
||||
msgid "created"
|
||||
msgstr "criado"
|
||||
|
||||
#: documents/models.py:190
|
||||
msgid "modified"
|
||||
msgstr "modificado"
|
||||
|
||||
#: documents/models.py:194
|
||||
msgid "storage type"
|
||||
msgstr "tipo de armazenamento"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "added"
|
||||
msgstr "adicionado"
|
||||
|
||||
#: documents/models.py:206
|
||||
msgid "filename"
|
||||
msgstr "nome do arquivo"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nome do arquivo atual armazenado"
|
||||
|
||||
#: documents/models.py:216
|
||||
msgid "archive filename"
|
||||
msgstr "nome do arquivo para arquivamento"
|
||||
|
||||
#: documents/models.py:222
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nome do arquivo para arquivamento armazenado"
|
||||
|
||||
#: documents/models.py:226
|
||||
msgid "archive serial number"
|
||||
msgstr "número de sério de arquivamento"
|
||||
|
||||
#: documents/models.py:231
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "A posição deste documento no seu arquivamento físico."
|
||||
|
||||
#: documents/models.py:237
|
||||
msgid "document"
|
||||
msgstr "documento"
|
||||
|
||||
#: documents/models.py:238
|
||||
msgid "documents"
|
||||
msgstr "documentos"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:326
|
||||
msgid "information"
|
||||
msgstr "informação"
|
||||
|
||||
#: documents/models.py:327
|
||||
msgid "warning"
|
||||
msgstr "aviso"
|
||||
|
||||
#: documents/models.py:328
|
||||
msgid "error"
|
||||
msgstr "erro"
|
||||
|
||||
#: documents/models.py:329
|
||||
msgid "critical"
|
||||
msgstr "crítico"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "group"
|
||||
msgstr "grupo"
|
||||
|
||||
#: documents/models.py:336
|
||||
msgid "message"
|
||||
msgstr "mensagem"
|
||||
|
||||
#: documents/models.py:339
|
||||
msgid "level"
|
||||
msgstr "nível"
|
||||
|
||||
#: documents/models.py:346
|
||||
msgid "log"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:347
|
||||
msgid "logs"
|
||||
msgstr "logs"
|
||||
|
||||
#: documents/models.py:358 documents/models.py:408
|
||||
msgid "saved view"
|
||||
msgstr "visualização"
|
||||
|
||||
#: documents/models.py:359
|
||||
msgid "saved views"
|
||||
msgstr "visualizações"
|
||||
|
||||
#: documents/models.py:362
|
||||
msgid "user"
|
||||
msgstr "usuário"
|
||||
|
||||
#: documents/models.py:368
|
||||
msgid "show on dashboard"
|
||||
msgstr "exibir no painel de controle"
|
||||
|
||||
#: documents/models.py:371
|
||||
msgid "show in sidebar"
|
||||
msgstr "exibir no painel lateral"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "sort field"
|
||||
msgstr "ordenar campo"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "sort reverse"
|
||||
msgstr "odernar reverso"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "title contains"
|
||||
msgstr "título contém"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "content contains"
|
||||
msgstr "conteúdo contém"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "ASN is"
|
||||
msgstr "NSA é"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "correspondent is"
|
||||
msgstr "correspondente é"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "document type is"
|
||||
msgstr "tipo de documento é"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "is in inbox"
|
||||
msgstr "é caixa de entrada"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "has tag"
|
||||
msgstr "contém etiqueta"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "has any tag"
|
||||
msgstr "contém qualquer etiqueta"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "created before"
|
||||
msgstr "criado antes de"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "created after"
|
||||
msgstr "criado depois de"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "created year is"
|
||||
msgstr "ano de criação é"
|
||||
|
||||
#: documents/models.py:395
|
||||
msgid "created month is"
|
||||
msgstr "mês de criação é"
|
||||
|
||||
#: documents/models.py:396
|
||||
msgid "created day is"
|
||||
msgstr "dia de criação é"
|
||||
|
||||
#: documents/models.py:397
|
||||
msgid "added before"
|
||||
msgstr "adicionado antes de"
|
||||
|
||||
#: documents/models.py:398
|
||||
msgid "added after"
|
||||
msgstr "adicionado depois de"
|
||||
|
||||
#: documents/models.py:399
|
||||
msgid "modified before"
|
||||
msgstr "modificado antes de"
|
||||
|
||||
#: documents/models.py:400
|
||||
msgid "modified after"
|
||||
msgstr "modificado depois de"
|
||||
|
||||
#: documents/models.py:401
|
||||
msgid "does not have tag"
|
||||
msgstr "não tem etiqueta"
|
||||
|
||||
#: documents/models.py:412
|
||||
msgid "rule type"
|
||||
msgstr "tipo de regra"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "value"
|
||||
msgstr "valor"
|
||||
|
||||
#: documents/models.py:422
|
||||
msgid "filter rule"
|
||||
msgstr "regra de filtragem"
|
||||
|
||||
#: documents/models.py:423
|
||||
msgid "filter rules"
|
||||
msgstr "regras de filtragem"
|
||||
|
||||
#: documents/serialisers.py:52
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr "Expressão regular inválida: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:378
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Tipo de arquivo %(type)s não suportado"
|
||||
|
||||
#: documents/templates/index.html:20
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng está carregando..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:13
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng saiu"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:41
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Sua sessão foi encerrada com sucesso. Até mais!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:42
|
||||
msgid "Sign in again"
|
||||
msgstr "Entre novamente"
|
||||
|
||||
#: documents/templates/registration/login.html:13
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Entrar no Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:42
|
||||
msgid "Please sign in."
|
||||
msgstr "Por favor, entre na sua conta"
|
||||
|
||||
#: documents/templates/registration/login.html:45
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Seu usuário e senha estão incorretos. Por favor, tente novamente."
|
||||
|
||||
#: documents/templates/registration/login.html:48
|
||||
msgid "Username"
|
||||
msgstr "Usuário"
|
||||
|
||||
#: documents/templates/registration/login.html:49
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Sign in"
|
||||
msgstr "Entrar"
|
||||
|
||||
#: paperless/settings.py:291
|
||||
msgid "English (US)"
|
||||
msgstr "Inglês (EUA)"
|
||||
|
||||
#: paperless/settings.py:292
|
||||
msgid "English (GB)"
|
||||
msgstr "Inglês (GB)"
|
||||
|
||||
#: paperless/settings.py:293
|
||||
msgid "German"
|
||||
msgstr "Alemão"
|
||||
|
||||
#: paperless/settings.py:294
|
||||
msgid "Dutch"
|
||||
msgstr "Holandês"
|
||||
|
||||
#: paperless/settings.py:295
|
||||
msgid "French"
|
||||
msgstr "Francês"
|
||||
|
||||
#: paperless/settings.py:296
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Português (Brasil)"
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administração do Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:25
|
||||
msgid "Filter"
|
||||
msgstr "Filtro"
|
||||
|
||||
#: paperless_mail/admin.py:27
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
"Paperless processará somente e-mails que se encaixam em TODOS os filtros "
|
||||
"abaixo."
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Actions"
|
||||
msgstr "Ações"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents"
|
||||
" were consumed from the mail. Mails without attachments will remain entirely"
|
||||
" untouched."
|
||||
msgstr ""
|
||||
"A ação se aplica ao e-mail. Essa ação só é executada quando documentos foram"
|
||||
" consumidos do e-mail. E-mails sem anexos permanecerão intactos."
|
||||
|
||||
#: paperless_mail/admin.py:46
|
||||
msgid "Metadata"
|
||||
msgstr "Metadados"
|
||||
|
||||
#: paperless_mail/admin.py:48
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
"Atribua metadados aos documentos consumidos por esta regra automaticamente. "
|
||||
"Se você não atribuir etiquetas, tipos ou correspondentes aqui, paperless "
|
||||
"ainda sim processará todas as regras de detecção que você definiu."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless mail"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "conta de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "contas de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Sem encriptação"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Usar SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Usar STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Servidor IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Porta IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgstr ""
|
||||
"É geralmente 143 para não encriptado e conexões STARTTLS, e 993 para "
|
||||
"conexões SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "segurança IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "usuário"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "senha"
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
msgid "mail rule"
|
||||
msgstr "regra de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "mail rules"
|
||||
msgstr "regras de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:67
|
||||
msgid "Only process attachments."
|
||||
msgstr "Processar somente anexos."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Processar todos os arquivos, incluindo anexos 'inline'."
|
||||
|
||||
#: paperless_mail/models.py:78
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marcar como lido, não processar e-mails lidos"
|
||||
|
||||
#: paperless_mail/models.py:79
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Sinalizar o e-mail, não processar e-mails sinalizados"
|
||||
|
||||
#: paperless_mail/models.py:80
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Mover para pasta especificada"
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Use subject as title"
|
||||
msgstr "Usar assunto como título"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Usar nome do arquivo anexo como título"
|
||||
|
||||
#: paperless_mail/models.py:99
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Não atribuir um correspondente"
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
msgid "Use mail address"
|
||||
msgstr "Usar endereço de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Usar nome (ou endereço de e-mail se não disponível)"
|
||||
|
||||
#: paperless_mail/models.py:105
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Usar correspondente selecionado abaixo"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "order"
|
||||
msgstr "ordem"
|
||||
|
||||
#: paperless_mail/models.py:120
|
||||
msgid "account"
|
||||
msgstr "conta"
|
||||
|
||||
#: paperless_mail/models.py:124
|
||||
msgid "folder"
|
||||
msgstr "pasta"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "filter from"
|
||||
msgstr "filtrar de"
|
||||
|
||||
#: paperless_mail/models.py:131
|
||||
msgid "filter subject"
|
||||
msgstr "filtrar assunto"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "filter body"
|
||||
msgstr "filtrar corpo"
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrar nome do arquivo anexo"
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid ""
|
||||
"Only consume documents which entirely match this filename if specified. "
|
||||
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
"Consumir somente documentos que correspondem a este nome de arquivo se especificado.\n"
|
||||
"Curingas como *.pdf ou *invoice* são permitidos. Sem diferenciação de maiúsculas e minúsculas."
|
||||
|
||||
#: paperless_mail/models.py:146
|
||||
msgid "maximum age"
|
||||
msgstr "idade máxima"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "Specified in days."
|
||||
msgstr "Especificada em dias."
|
||||
|
||||
#: paperless_mail/models.py:151
|
||||
msgid "attachment type"
|
||||
msgstr "tipo de anexo"
|
||||
|
||||
#: paperless_mail/models.py:154
|
||||
msgid ""
|
||||
"Inline attachments include embedded images, so it's best to combine this "
|
||||
"option with a filename filter."
|
||||
msgstr ""
|
||||
"Anexos inline incluem imagens inseridas, por isso é melhor combinar essa "
|
||||
"opção com um filtro de nome de arquivo."
|
||||
|
||||
#: paperless_mail/models.py:159
|
||||
msgid "action"
|
||||
msgstr "ação"
|
||||
|
||||
#: paperless_mail/models.py:165
|
||||
msgid "action parameter"
|
||||
msgstr "parâmetro da ação"
|
||||
|
||||
#: paperless_mail/models.py:167
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action."
|
||||
msgstr ""
|
||||
"Parâmetro adicional para a ação selecionada acima, por exemplo: a pasta de "
|
||||
"destino da ação de mover pasta."
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
msgid "assign title from"
|
||||
msgstr "atribuir título de"
|
||||
|
||||
#: paperless_mail/models.py:183
|
||||
msgid "assign this tag"
|
||||
msgstr "atribuir esta etiqueta"
|
||||
|
||||
#: paperless_mail/models.py:191
|
||||
msgid "assign this document type"
|
||||
msgstr "atribuir este tipo de documento"
|
||||
|
||||
#: paperless_mail/models.py:195
|
||||
msgid "assign correspondent from"
|
||||
msgstr "atribuir correspondente de"
|
||||
|
||||
#: paperless_mail/models.py:205
|
||||
msgid "assign this correspondent"
|
||||
msgstr "atribuir este correspondente"
|
@@ -112,7 +112,10 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication'
|
||||
]
|
||||
],
|
||||
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
|
||||
'DEFAULT_VERSION': '1',
|
||||
'ALLOWED_VERSIONS': ['1', '2']
|
||||
}
|
||||
|
||||
if DEBUG:
|
||||
@@ -142,7 +145,7 @@ ASGI_APPLICATION = "paperless.asgi.application"
|
||||
|
||||
STATIC_URL = os.getenv("PAPERLESS_STATIC_URL", "/static/")
|
||||
|
||||
# what is this used for?
|
||||
# TODO: what is this used for?
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
@@ -292,7 +295,8 @@ LANGUAGES = [
|
||||
("en-gb", _("English (GB)")),
|
||||
("de", _("German")),
|
||||
("nl-nl", _("Dutch")),
|
||||
("fr", _("French"))
|
||||
("fr", _("French")),
|
||||
("pt-br", _("Portuguese (Brazil)"))
|
||||
]
|
||||
|
||||
LOCALE_PATHS = [
|
||||
@@ -425,6 +429,12 @@ THREADS_PER_WORKER = os.getenv("PAPERLESS_THREADS_PER_WORKER", default_threads_p
|
||||
|
||||
CONSUMER_POLLING = int(os.getenv("PAPERLESS_CONSUMER_POLLING", 0))
|
||||
|
||||
CONSUMER_POLLING_DELAY = int(os.getenv("PAPERLESS_CONSUMER_POLLING_DELAY", 5))
|
||||
|
||||
CONSUMER_POLLING_RETRY_COUNT = int(
|
||||
os.getenv("PAPERLESS_CONSUMER_POLLING_RETRY_COUNT", 5)
|
||||
)
|
||||
|
||||
CONSUMER_DELETE_DUPLICATES = __get_boolean("PAPERLESS_CONSUMER_DELETE_DUPLICATES")
|
||||
|
||||
CONSUMER_RECURSIVE = __get_boolean("PAPERLESS_CONSUMER_RECURSIVE")
|
||||
@@ -449,6 +459,14 @@ OCR_MODE = os.getenv("PAPERLESS_OCR_MODE", "skip")
|
||||
|
||||
OCR_IMAGE_DPI = os.getenv("PAPERLESS_OCR_IMAGE_DPI")
|
||||
|
||||
OCR_CLEAN = os.getenv("PAPERLESS_OCR_CLEAN", "clean")
|
||||
|
||||
OCR_DESKEW = __get_boolean("PAPERLESS_OCR_DESKEW", "true")
|
||||
|
||||
OCR_ROTATE_PAGES = __get_boolean("PAPERLESS_OCR_ROTATE_PAGES", "true")
|
||||
|
||||
OCR_ROTATE_PAGES_THRESHOLD = float(os.getenv("PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD", 12.0))
|
||||
|
||||
OCR_USER_ARGS = os.getenv("PAPERLESS_OCR_USER_ARGS", "{}")
|
||||
|
||||
# GNUPG needs a home directory for some reason
|
||||
|
@@ -23,7 +23,8 @@ from documents.views import (
|
||||
PostDocumentView,
|
||||
SavedViewViewSet,
|
||||
BulkEditView,
|
||||
SelectionDataView
|
||||
SelectionDataView,
|
||||
BulkDownloadView
|
||||
)
|
||||
from paperless.views import FaviconView
|
||||
|
||||
@@ -63,6 +64,9 @@ urlpatterns = [
|
||||
re_path(r"^documents/selection_data/", SelectionDataView.as_view(),
|
||||
name="selection_data"),
|
||||
|
||||
re_path(r"^documents/bulk_download/", BulkDownloadView.as_view(),
|
||||
name="bulk_download"),
|
||||
|
||||
path('token/', views.obtain_auth_token)
|
||||
|
||||
] + api_router.urls)),
|
||||
|
@@ -1 +1 @@
|
||||
__version__ = (1, 1, 4)
|
||||
__version__ = (1, 2, 1)
|
||||
|
@@ -9,6 +9,10 @@ from documents.parsers import DocumentParser, ParseError, \
|
||||
make_thumbnail_from_pdf
|
||||
|
||||
|
||||
class NoTextFoundException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RasterisedDocumentParser(DocumentParser):
|
||||
"""
|
||||
This parser uses Tesseract to try and get some text out of a rasterised
|
||||
@@ -18,12 +22,13 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
logging_name = "paperless.parsing.tesseract"
|
||||
|
||||
def extract_metadata(self, document_path, mime_type):
|
||||
import pikepdf
|
||||
|
||||
namespace_pattern = re.compile(r"\{(.*)\}(.*)")
|
||||
|
||||
result = []
|
||||
if mime_type == 'application/pdf':
|
||||
import pikepdf
|
||||
|
||||
namespace_pattern = re.compile(r"\{(.*)\}(.*)")
|
||||
|
||||
pdf = pikepdf.open(document_path)
|
||||
meta = pdf.open_metadata()
|
||||
for key, value in meta.items():
|
||||
@@ -48,7 +53,9 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
|
||||
def get_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
return make_thumbnail_from_pdf(
|
||||
document_path, self.tempdir, self.logging_group)
|
||||
self.archive_path or document_path,
|
||||
self.tempdir,
|
||||
self.logging_group)
|
||||
|
||||
def is_image(self, mime_type):
|
||||
return mime_type in [
|
||||
@@ -88,125 +95,202 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
f"Error while calculating DPI for image {image}: {e}")
|
||||
return None
|
||||
|
||||
def extract_text(self, sidecar_file, pdf_file):
|
||||
if sidecar_file and os.path.isfile(sidecar_file):
|
||||
with open(sidecar_file, "r") as f:
|
||||
text = f.read()
|
||||
|
||||
if "[OCR skipped on page" not in text:
|
||||
# This happens when there's already text in the input file.
|
||||
# The sidecar file will only contain text for OCR'ed pages.
|
||||
self.log("debug", "Using text from sidecar file")
|
||||
return text
|
||||
else:
|
||||
self.log("debug", "Incomplete sidecar file: discarding.")
|
||||
|
||||
# no success with the sidecar file, try PDF
|
||||
|
||||
if not os.path.isfile(pdf_file):
|
||||
return None
|
||||
|
||||
from pdfminer.high_level import extract_text
|
||||
from pdfminer.pdftypes import PDFException
|
||||
|
||||
try:
|
||||
text = extract_text(pdf_file)
|
||||
stripped = strip_excess_whitespace(text)
|
||||
self.log("debug", f"Extracted text from PDF file {pdf_file}")
|
||||
return stripped
|
||||
except PDFException:
|
||||
# probably not a PDF file.
|
||||
return None
|
||||
|
||||
def construct_ocrmypdf_parameters(self,
|
||||
input_file,
|
||||
mime_type,
|
||||
output_file,
|
||||
sidecar_file,
|
||||
safe_fallback=False):
|
||||
ocrmypdf_args = {
|
||||
'input_file': input_file,
|
||||
'output_file': output_file,
|
||||
# need to use threads, since this will be run in daemonized
|
||||
# processes by django-q.
|
||||
'use_threads': True,
|
||||
'jobs': settings.THREADS_PER_WORKER,
|
||||
'language': settings.OCR_LANGUAGE,
|
||||
'output_type': settings.OCR_OUTPUT_TYPE,
|
||||
'progress_bar': False
|
||||
}
|
||||
|
||||
if settings.OCR_MODE == 'force' or safe_fallback:
|
||||
ocrmypdf_args['force_ocr'] = True
|
||||
elif settings.OCR_MODE in ['skip', 'skip_noarchive']:
|
||||
ocrmypdf_args['skip_text'] = True
|
||||
elif settings.OCR_MODE == 'redo':
|
||||
ocrmypdf_args['redo_ocr'] = True
|
||||
else:
|
||||
raise ParseError(
|
||||
f"Invalid ocr mode: {settings.OCR_MODE}")
|
||||
|
||||
if settings.OCR_CLEAN == 'clean':
|
||||
ocrmypdf_args['clean'] = True
|
||||
elif settings.OCR_CLEAN == 'clean-final':
|
||||
if settings.OCR_MODE == 'redo':
|
||||
ocrmypdf_args['clean'] = True
|
||||
else:
|
||||
ocrmypdf_args['clean_final'] = True
|
||||
|
||||
if settings.OCR_DESKEW and not settings.OCR_MODE == 'redo':
|
||||
ocrmypdf_args['deskew'] = True
|
||||
|
||||
if settings.OCR_ROTATE_PAGES:
|
||||
ocrmypdf_args['rotate_pages'] = True
|
||||
ocrmypdf_args['rotate_pages_threshold'] = settings.OCR_ROTATE_PAGES_THRESHOLD # NOQA: E501
|
||||
|
||||
if settings.OCR_PAGES > 0:
|
||||
ocrmypdf_args['pages'] = f"1-{settings.OCR_PAGES}"
|
||||
else:
|
||||
# sidecar is incompatible with pages
|
||||
ocrmypdf_args['sidecar'] = sidecar_file
|
||||
|
||||
if self.is_image(mime_type):
|
||||
dpi = self.get_dpi(input_file)
|
||||
a4_dpi = self.calculate_a4_dpi(input_file)
|
||||
if dpi:
|
||||
self.log(
|
||||
"debug",
|
||||
f"Detected DPI for image {input_file}: {dpi}"
|
||||
)
|
||||
ocrmypdf_args['image_dpi'] = dpi
|
||||
elif settings.OCR_IMAGE_DPI:
|
||||
ocrmypdf_args['image_dpi'] = settings.OCR_IMAGE_DPI
|
||||
elif a4_dpi:
|
||||
ocrmypdf_args['image_dpi'] = a4_dpi
|
||||
else:
|
||||
raise ParseError(
|
||||
f"Cannot produce archive PDF for image {input_file}, "
|
||||
f"no DPI information is present in this image and "
|
||||
f"OCR_IMAGE_DPI is not set.")
|
||||
|
||||
if settings.OCR_USER_ARGS and not safe_fallback:
|
||||
try:
|
||||
user_args = json.loads(settings.OCR_USER_ARGS)
|
||||
ocrmypdf_args = {**ocrmypdf_args, **user_args}
|
||||
except Exception as e:
|
||||
self.log(
|
||||
"warning",
|
||||
f"There is an issue with PAPERLESS_OCR_USER_ARGS, so "
|
||||
f"they will not be used. Error: {e}")
|
||||
|
||||
return ocrmypdf_args
|
||||
|
||||
def parse(self, document_path, mime_type, file_name=None):
|
||||
import ocrmypdf
|
||||
from ocrmypdf import InputFileError, EncryptedPdfError
|
||||
# This forces tesseract to use one core per page.
|
||||
os.environ['OMP_THREAD_LIMIT'] = "1"
|
||||
|
||||
mode = settings.OCR_MODE
|
||||
text_original = self.extract_text(None, document_path)
|
||||
original_has_text = text_original and len(text_original) > 50
|
||||
|
||||
text_original = get_text_from_pdf(document_path)
|
||||
has_text = text_original and len(text_original) > 50
|
||||
|
||||
if mode == "skip_noarchive" and has_text:
|
||||
if settings.OCR_MODE == "skip_noarchive" and original_has_text:
|
||||
self.log("debug",
|
||||
"Document has text, skipping OCRmyPDF entirely.")
|
||||
self.text = text_original
|
||||
return
|
||||
|
||||
if mode in ['skip', 'skip_noarchive'] and not has_text:
|
||||
# upgrade to redo, since there appears to be no text in the
|
||||
# document. This happens to some weird encrypted documents or
|
||||
# documents with failed OCR attempts for which OCRmyPDF will
|
||||
# still report that there actually is text in them.
|
||||
self.log("debug",
|
||||
"No text was found in the document and skip is "
|
||||
"specified. Upgrading OCR mode to redo.")
|
||||
mode = "redo"
|
||||
import ocrmypdf
|
||||
from ocrmypdf import InputFileError, EncryptedPdfError
|
||||
|
||||
archive_path = os.path.join(self.tempdir, "archive.pdf")
|
||||
sidecar_file = os.path.join(self.tempdir, "sidecar.txt")
|
||||
|
||||
ocr_args = {
|
||||
'input_file': document_path,
|
||||
'output_file': archive_path,
|
||||
'use_threads': True,
|
||||
'jobs': settings.THREADS_PER_WORKER,
|
||||
'language': settings.OCR_LANGUAGE,
|
||||
'output_type': settings.OCR_OUTPUT_TYPE,
|
||||
'progress_bar': False,
|
||||
'clean': True
|
||||
}
|
||||
|
||||
if settings.OCR_PAGES > 0:
|
||||
ocr_args['pages'] = f"1-{settings.OCR_PAGES}"
|
||||
|
||||
# Mode selection.
|
||||
|
||||
if mode in ['skip', 'skip_noarchive']:
|
||||
ocr_args['skip_text'] = True
|
||||
elif mode == 'redo':
|
||||
ocr_args['redo_ocr'] = True
|
||||
elif mode == 'force':
|
||||
ocr_args['force_ocr'] = True
|
||||
else:
|
||||
raise ParseError(
|
||||
f"Invalid ocr mode: {mode}")
|
||||
|
||||
if self.is_image(mime_type):
|
||||
dpi = self.get_dpi(document_path)
|
||||
a4_dpi = self.calculate_a4_dpi(document_path)
|
||||
if dpi:
|
||||
self.log(
|
||||
"debug",
|
||||
f"Detected DPI for image {document_path}: {dpi}"
|
||||
)
|
||||
ocr_args['image_dpi'] = dpi
|
||||
elif settings.OCR_IMAGE_DPI:
|
||||
ocr_args['image_dpi'] = settings.OCR_IMAGE_DPI
|
||||
elif a4_dpi:
|
||||
ocr_args['image_dpi'] = a4_dpi
|
||||
else:
|
||||
raise ParseError(
|
||||
f"Cannot produce archive PDF for image {document_path}, "
|
||||
f"no DPI information is present in this image and "
|
||||
f"OCR_IMAGE_DPI is not set.")
|
||||
|
||||
if settings.OCR_USER_ARGS:
|
||||
try:
|
||||
user_args = json.loads(settings.OCR_USER_ARGS)
|
||||
ocr_args = {**ocr_args, **user_args}
|
||||
except Exception as e:
|
||||
self.log(
|
||||
"warning",
|
||||
f"There is an issue with PAPERLESS_OCR_USER_ARGS, so "
|
||||
f"they will not be used: {e}")
|
||||
|
||||
# This forces tesseract to use one core per page.
|
||||
os.environ['OMP_THREAD_LIMIT'] = "1"
|
||||
args = self.construct_ocrmypdf_parameters(
|
||||
document_path, mime_type, archive_path, sidecar_file)
|
||||
|
||||
try:
|
||||
self.log("debug",
|
||||
f"Calling OCRmyPDF with {str(ocr_args)}")
|
||||
ocrmypdf.ocr(**ocr_args)
|
||||
# success! announce results
|
||||
self.log("debug", f"Calling OCRmyPDF with args: {args}")
|
||||
ocrmypdf.ocr(**args)
|
||||
|
||||
self.archive_path = archive_path
|
||||
self.text = get_text_from_pdf(archive_path)
|
||||
self.text = self.extract_text(sidecar_file, archive_path)
|
||||
|
||||
except (InputFileError, EncryptedPdfError) as e:
|
||||
|
||||
self.log("debug",
|
||||
f"Encountered an error: {e}. Trying to use text from "
|
||||
f"original.")
|
||||
# This happens with some PDFs when used with the redo_ocr option.
|
||||
# This is not the end of the world, we'll just use what we already
|
||||
# have in the document.
|
||||
self.text = text_original
|
||||
# Also, no archived file.
|
||||
if not self.text:
|
||||
# However, if we don't have anything, fail:
|
||||
raise NoTextFoundException(
|
||||
"No text was found in the original document")
|
||||
except EncryptedPdfError:
|
||||
self.log("warning",
|
||||
"This file is encrypted, OCR is impossible. Using "
|
||||
"any text present in the original file.")
|
||||
if original_has_text:
|
||||
self.text = text_original
|
||||
except (NoTextFoundException, InputFileError) as e:
|
||||
self.log("exception",
|
||||
f"Encountered the following error while running OCR, "
|
||||
f"attempting force OCR to get the text.")
|
||||
|
||||
archive_path_fallback = os.path.join(
|
||||
self.tempdir, "archive-fallback.pdf")
|
||||
sidecar_file_fallback = os.path.join(
|
||||
self.tempdir, "sidecar-fallback.txt")
|
||||
|
||||
# Attempt to run OCR with safe settings.
|
||||
|
||||
args = self.construct_ocrmypdf_parameters(
|
||||
document_path, mime_type,
|
||||
archive_path_fallback, sidecar_file_fallback,
|
||||
safe_fallback=True
|
||||
)
|
||||
|
||||
try:
|
||||
self.log("debug",
|
||||
f"Fallback: Calling OCRmyPDF with args: {args}")
|
||||
ocrmypdf.ocr(**args)
|
||||
|
||||
# Don't return the archived file here, since this file
|
||||
# is bigger and blurry due to --force-ocr.
|
||||
|
||||
self.text = self.extract_text(
|
||||
sidecar_file_fallback, archive_path_fallback)
|
||||
|
||||
except Exception as e:
|
||||
# If this fails, we have a serious issue at hand.
|
||||
raise ParseError(f"{e.__class__.__name__}: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
# Anything else is probably serious.
|
||||
raise ParseError(f"{e.__class__.__name__}: {str(e)}")
|
||||
|
||||
# As a last resort, if we still don't have any text for any reason,
|
||||
# try to extract the text from the original document.
|
||||
if not self.text:
|
||||
# This may happen for files that don't have any text.
|
||||
self.log(
|
||||
'warning',
|
||||
f"Document {document_path} does not have any text. "
|
||||
f"This is probably an error or you tried to add an image "
|
||||
f"without text, or something is wrong with this document.")
|
||||
self.text = ""
|
||||
if original_has_text:
|
||||
self.text = text_original
|
||||
else:
|
||||
self.log(
|
||||
"warning",
|
||||
f"No text was found in {document_path}, the content will "
|
||||
f"be empty."
|
||||
)
|
||||
|
||||
|
||||
def strip_excess_whitespace(text):
|
||||
@@ -221,21 +305,3 @@ def strip_excess_whitespace(text):
|
||||
|
||||
# TODO: this needs a rework
|
||||
return no_trailing_whitespace.strip()
|
||||
|
||||
|
||||
def get_text_from_pdf(pdf_file):
|
||||
import pdftotext
|
||||
|
||||
if not os.path.isfile(pdf_file):
|
||||
return None
|
||||
|
||||
with open(pdf_file, "rb") as f:
|
||||
try:
|
||||
pdf = pdftotext.PDF(f)
|
||||
except pdftotext.Error:
|
||||
# might not be a PDF file
|
||||
return None
|
||||
|
||||
text = "\n".join(pdf)
|
||||
|
||||
return strip_excess_whitespace(text)
|
||||
|
BIN
src/paperless_tesseract/tests/samples/encrypted.pdf
Normal file
BIN
src/paperless_tesseract/tests/samples/encrypted.pdf
Normal file
Binary file not shown.
BIN
src/paperless_tesseract/tests/samples/multi-page-mixed.pdf
Normal file
BIN
src/paperless_tesseract/tests/samples/multi-page-mixed.pdf
Normal file
Binary file not shown.
BIN
src/paperless_tesseract/tests/samples/rotated.pdf
Normal file
BIN
src/paperless_tesseract/tests/samples/rotated.pdf
Normal file
Binary file not shown.
@@ -7,7 +7,7 @@ from django.test import TestCase, override_settings
|
||||
|
||||
from documents.parsers import ParseError, run_convert
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from paperless_tesseract.parsers import RasterisedDocumentParser, get_text_from_pdf, strip_excess_whitespace
|
||||
from paperless_tesseract.parsers import RasterisedDocumentParser, strip_excess_whitespace
|
||||
|
||||
image_to_string_calls = []
|
||||
|
||||
@@ -38,7 +38,12 @@ class TestParser(DirectoriesMixin, TestCase):
|
||||
|
||||
def assertContainsStrings(self, content, strings):
|
||||
# Asserts that all strings appear in content, in the given order.
|
||||
indices = [content.index(s) for s in strings]
|
||||
indices = []
|
||||
for s in strings:
|
||||
if s in content:
|
||||
indices.append(content.index(s))
|
||||
else:
|
||||
self.fail(f"'{s}' is not in '{content}'")
|
||||
self.assertListEqual(indices, sorted(indices))
|
||||
|
||||
text_cases = [
|
||||
@@ -69,7 +74,8 @@ class TestParser(DirectoriesMixin, TestCase):
|
||||
SAMPLE_FILES = os.path.join(os.path.dirname(__file__), "samples")
|
||||
|
||||
def test_get_text_from_pdf(self):
|
||||
text = get_text_from_pdf(os.path.join(self.SAMPLE_FILES, 'simple-digital.pdf'))
|
||||
parser = RasterisedDocumentParser(uuid.uuid4())
|
||||
text = parser.extract_text(None, os.path.join(self.SAMPLE_FILES, 'simple-digital.pdf'))
|
||||
|
||||
self.assertContainsStrings(text.strip(), ["This is a test document."])
|
||||
|
||||
@@ -129,15 +135,21 @@ class TestParser(DirectoriesMixin, TestCase):
|
||||
self.assertIsNone(parser.archive_path)
|
||||
self.assertContainsStrings(parser.get_text(), ["Please enter your name in here:", "This is a PDF document with a form."])
|
||||
|
||||
@override_settings(OCR_MODE="redo")
|
||||
@mock.patch("paperless_tesseract.parsers.get_text_from_pdf", lambda _: None)
|
||||
def test_with_form_error_notext(self):
|
||||
@override_settings(OCR_MODE="skip")
|
||||
def test_encrypted(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
|
||||
def f():
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "with-form.pdf"), "application/pdf")
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "encrypted.pdf"), "application/pdf")
|
||||
|
||||
self.assertRaises(ParseError, f)
|
||||
self.assertIsNone(parser.archive_path)
|
||||
self.assertContainsStrings(parser.get_text(), ["This is a digitally signed PDF, created with Acrobat Pro for the Paperless project to enable", "automated testing of signed/encrypted PDFs"])
|
||||
|
||||
@override_settings(OCR_MODE="redo")
|
||||
def test_with_form_error_notext(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "with-form.pdf"), "application/pdf")
|
||||
|
||||
self.assertContainsStrings(parser.get_text(), ["Please enter your name in here:", "This is a PDF document with a form."])
|
||||
|
||||
@override_settings(OCR_MODE="force")
|
||||
def test_with_form_force(self):
|
||||
@@ -253,9 +265,82 @@ class TestParser(DirectoriesMixin, TestCase):
|
||||
def test_skip_noarchive_notext(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "multi-page-images.pdf"), "application/pdf")
|
||||
self.assertTrue(os.path.join(parser.archive_path))
|
||||
self.assertTrue(os.path.isfile(parser.archive_path))
|
||||
self.assertContainsStrings(parser.get_text().lower(), ["page 1", "page 2", "page 3"])
|
||||
|
||||
@override_settings(OCR_MODE="skip")
|
||||
def test_multi_page_mixed(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "multi-page-mixed.pdf"), "application/pdf")
|
||||
self.assertTrue(os.path.isfile(parser.archive_path))
|
||||
self.assertContainsStrings(parser.get_text().lower(), ["page 1", "page 2", "page 3", "page 4", "page 5", "page 6"])
|
||||
|
||||
with open(os.path.join(parser.tempdir, "sidecar.txt")) as f:
|
||||
sidecar = f.read()
|
||||
|
||||
self.assertIn("[OCR skipped on page 4]", sidecar)
|
||||
self.assertIn("[OCR skipped on page 5]", sidecar)
|
||||
self.assertIn("[OCR skipped on page 6]", sidecar)
|
||||
|
||||
@override_settings(OCR_MODE="skip_noarchive")
|
||||
def test_multi_page_mixed_no_archive(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "multi-page-mixed.pdf"), "application/pdf")
|
||||
self.assertIsNone(parser.archive_path)
|
||||
self.assertContainsStrings(parser.get_text().lower(), ["page 4", "page 5", "page 6"])
|
||||
|
||||
@override_settings(OCR_MODE="skip", OCR_ROTATE_PAGES=True)
|
||||
def test_rotate(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
parser.parse(os.path.join(self.SAMPLE_FILES, "rotated.pdf"), "application/pdf")
|
||||
self.assertContainsStrings(parser.get_text(), [
|
||||
"This is the text that appears on the first page. It’s a lot of text.",
|
||||
"Even if the pages are rotated, OCRmyPDF still gets the job done.",
|
||||
"This is a really weird file with lots of nonsense text.",
|
||||
"If you read this, it’s your own fault. Also check your screen orientation."
|
||||
])
|
||||
|
||||
def test_ocrmypdf_parameters(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
params = parser.construct_ocrmypdf_parameters(input_file="input.pdf", output_file="output.pdf",
|
||||
sidecar_file="sidecar.txt", mime_type="application/pdf",
|
||||
safe_fallback=False)
|
||||
|
||||
self.assertEqual(params['input_file'], "input.pdf")
|
||||
self.assertEqual(params['output_file'], "output.pdf")
|
||||
self.assertEqual(params['sidecar'], "sidecar.txt")
|
||||
|
||||
with override_settings(OCR_CLEAN="none"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertNotIn("clean", params)
|
||||
self.assertNotIn("clean_final", params)
|
||||
|
||||
with override_settings(OCR_CLEAN="clean"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertTrue(params['clean'])
|
||||
self.assertNotIn("clean_final", params)
|
||||
|
||||
with override_settings(OCR_CLEAN="clean-final", OCR_MODE="skip"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertTrue(params['clean_final'])
|
||||
self.assertNotIn("clean", params)
|
||||
|
||||
with override_settings(OCR_CLEAN="clean-final", OCR_MODE="redo"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertTrue(params['clean'])
|
||||
self.assertNotIn("clean_final", params)
|
||||
|
||||
with override_settings(OCR_DESKEW=True, OCR_MODE="skip"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertTrue(params['deskew'])
|
||||
|
||||
with override_settings(OCR_DESKEW=True, OCR_MODE="redo"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertNotIn('deskew', params)
|
||||
|
||||
with override_settings(OCR_DESKEW=False, OCR_MODE="skip"):
|
||||
params = parser.construct_ocrmypdf_parameters("", "", "", "")
|
||||
self.assertNotIn('deskew', params)
|
||||
|
||||
class TestParserFileTypes(DirectoriesMixin, TestCase):
|
||||
|
||||
|
Reference in New Issue
Block a user