From 0c670e25c348f3f9bbd845e6c13b143074d99c3e Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Tue, 3 May 2022 11:15:31 -0700 Subject: [PATCH 001/291] Includes a version.json file with the current version in export. On import, catch certain errors and check the version if possible --- .../management/commands/document_exporter.py | 9 ++++++- .../management/commands/document_importer.py | 24 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index b110475a5..27f1fd8a9 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -22,6 +22,7 @@ from documents.settings import EXPORTER_ARCHIVE_NAME from documents.settings import EXPORTER_FILE_NAME from documents.settings import EXPORTER_THUMBNAIL_NAME from filelock import FileLock +from paperless import version from paperless.db import GnuPG from paperless_mail.models import MailAccount from paperless_mail.models import MailRule @@ -232,12 +233,18 @@ class Command(BaseCommand): archive_target, ) - # 4. write manifest to target forlder + # 4.1 write manifest to target folder manifest_path = os.path.abspath(os.path.join(self.target, "manifest.json")) with open(manifest_path, "w") as f: json.dump(manifest, f, indent=2) + # 4.2 write version information to target folder + version_path = os.path.abspath(os.path.join(self.target, "version.json")) + + with open(version_path, "w") as f: + json.dump({"version": version.__full_version_str__}, f, indent=2) + if self.delete: # 5. Remove files which we did not explicitly export in this run diff --git a/src/documents/management/commands/document_importer.py b/src/documents/management/commands/document_importer.py index d1ae33afb..398bc05b4 100644 --- a/src/documents/management/commands/document_importer.py +++ b/src/documents/management/commands/document_importer.py @@ -6,9 +6,11 @@ from contextlib import contextmanager import tqdm from django.conf import settings +from django.core.exceptions import FieldDoesNotExist from django.core.management import call_command from django.core.management.base import BaseCommand from django.core.management.base import CommandError +from django.core.serializers.base import DeserializationError from django.db.models.signals import m2m_changed from django.db.models.signals import post_save from documents.models import Document @@ -16,6 +18,7 @@ from documents.settings import EXPORTER_ARCHIVE_NAME from documents.settings import EXPORTER_FILE_NAME from documents.settings import EXPORTER_THUMBNAIL_NAME from filelock import FileLock +from paperless import version from ...file_handling import create_source_path_directory from ...signals.handlers import update_filename_and_move_files @@ -53,6 +56,7 @@ class Command(BaseCommand): BaseCommand.__init__(self, *args, **kwargs) self.source = None self.manifest = None + self.version = None def handle(self, *args, **options): @@ -72,6 +76,11 @@ class Command(BaseCommand): with open(manifest_path) as f: self.manifest = json.load(f) + version_path = os.path.join(self.source, "version.json") + if os.path.exists(version_path): + with open(version_path) as f: + self.version = json.load(f)["version"] + self._check_manifest() with disable_signal( post_save, @@ -84,7 +93,20 @@ class Command(BaseCommand): sender=Document.tags.through, ): # Fill up the database with whatever is in the manifest - call_command("loaddata", manifest_path) + try: + call_command("loaddata", manifest_path) + except (FieldDoesNotExist, DeserializationError) as e: + if ( + self.version is not None + and self.version != version.__full_version_str__ + ): + raise CommandError( + "Error loading database, version mismatch. " + f"Currently {version.__full_version_str__}," + f" importing {self.version}", + ) from e + else: + raise CommandError("Error loading database") from e self._import_files_from_manifest(options["no_progress_bar"]) From 0129675c1a61ccbbe62c99ecd3dbb81170b1c757 Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Wed, 4 May 2022 19:41:49 -0700 Subject: [PATCH 002/291] Uses the correct styling for output messages --- .pre-commit-config.yaml | 2 +- .../management/commands/document_archiver.py | 2 +- .../management/commands/document_importer.py | 22 ++++++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f0bf9bace..fb824db9f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: exclude: "(^Pipfile\\.lock$)" # Python hooks - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports exclude: "(migrations)" diff --git a/src/documents/management/commands/document_archiver.py b/src/documents/management/commands/document_archiver.py index f33ccd7ce..bb376c2dd 100644 --- a/src/documents/management/commands/document_archiver.py +++ b/src/documents/management/commands/document_archiver.py @@ -152,4 +152,4 @@ class Command(BaseCommand): ), ) except KeyboardInterrupt: - print("Aborting...") + self.stdout.write(self.style.NOTICE(("Aborting..."))) diff --git a/src/documents/management/commands/document_importer.py b/src/documents/management/commands/document_importer.py index 398bc05b4..18bbe6c7f 100644 --- a/src/documents/management/commands/document_importer.py +++ b/src/documents/management/commands/document_importer.py @@ -80,6 +80,18 @@ class Command(BaseCommand): if os.path.exists(version_path): with open(version_path) as f: self.version = json.load(f)["version"] + # Provide an initial warning if needed to the user + if self.version != version.__full_version_str__: + self.stdout.write( + self.style.WARNING( + "Version mismatch:" + f" {self.version} vs {version.__full_version_str__}." + " Continuing, but import may fail", + ), + ) + + else: + self.stdout.write(self.style.WARNING("No version.json file located")) self._check_manifest() with disable_signal( @@ -110,8 +122,12 @@ class Command(BaseCommand): self._import_files_from_manifest(options["no_progress_bar"]) - print("Updating search index...") - call_command("document_index", "reindex") + self.stdout.write("Updating search index...") + call_command( + "document_index", + "reindex", + no_progress_bar=options["no_progress_bar"], + ) @staticmethod def _check_manifest_exists(path): @@ -154,7 +170,7 @@ class Command(BaseCommand): os.makedirs(settings.THUMBNAIL_DIR, exist_ok=True) os.makedirs(settings.ARCHIVE_DIR, exist_ok=True) - print("Copy files into paperless...") + self.stdout.write("Copy files into paperless...") manifest_documents = list( filter(lambda r: r["model"] == "documents.document", self.manifest), From 070b648e845628a65c280fb8e7d05ae1dc67d111 Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Thu, 5 May 2022 09:17:51 -0700 Subject: [PATCH 003/291] Improves the output to the user --- .../management/commands/document_importer.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/documents/management/commands/document_importer.py b/src/documents/management/commands/document_importer.py index 18bbe6c7f..92742a487 100644 --- a/src/documents/management/commands/document_importer.py +++ b/src/documents/management/commands/document_importer.py @@ -70,13 +70,13 @@ class Command(BaseCommand): if not os.access(self.source, os.R_OK): raise CommandError("That path doesn't appear to be readable") - manifest_path = os.path.join(self.source, "manifest.json") + manifest_path = os.path.normpath(os.path.join(self.source, "manifest.json")) self._check_manifest_exists(manifest_path) with open(manifest_path) as f: self.manifest = json.load(f) - version_path = os.path.join(self.source, "version.json") + version_path = os.path.normpath(os.path.join(self.source, "version.json")) if os.path.exists(version_path): with open(version_path) as f: self.version = json.load(f)["version"] @@ -91,7 +91,7 @@ class Command(BaseCommand): ) else: - self.stdout.write(self.style.WARNING("No version.json file located")) + self.stdout.write(self.style.NOTICE("No version.json file located")) self._check_manifest() with disable_signal( @@ -108,17 +108,24 @@ class Command(BaseCommand): try: call_command("loaddata", manifest_path) except (FieldDoesNotExist, DeserializationError) as e: + self.stdout.write(self.style.ERROR("Database import failed")) if ( self.version is not None and self.version != version.__full_version_str__ ): - raise CommandError( - "Error loading database, version mismatch. " - f"Currently {version.__full_version_str__}," - f" importing {self.version}", - ) from e + self.stdout.write( + self.style.ERROR( + "Version mismatch: " + f"Currently {version.__full_version_str__}," + f" importing {self.version}", + ), + ) + raise e else: - raise CommandError("Error loading database") from e + self.stdout.write( + self.style.ERROR("No version information present"), + ) + raise e self._import_files_from_manifest(options["no_progress_bar"]) From 7f88ec096594588e755f3c82d9f615a7e19c69c8 Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Thu, 5 May 2022 09:23:15 -0700 Subject: [PATCH 004/291] Adds documentation note about version matching --- docs/administration.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/administration.rst b/docs/administration.rst index 1139c3a69..f6b0ed659 100644 --- a/docs/administration.rst +++ b/docs/administration.rst @@ -287,6 +287,10 @@ When you use the provided docker compose script, put the export inside the ``export`` folder in your paperless source directory. Specify ``../export`` as the ``source``. +.. note:: + + Importing from a previous version of Paperless may work, but for best results + it is suggested to match the versions. .. _utilities-retagger: From 2e47b6b22fe7606de62b7be9057cbd33f0f9a9aa Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 8 May 2022 09:03:29 -0700 Subject: [PATCH 005/291] loading indicator for dashboard widgets --- .../saved-view-widget/saved-view-widget.component.html | 2 +- .../widgets/saved-view-widget/saved-view-widget.component.ts | 4 ++++ .../statistics-widget/statistics-widget.component.html | 2 +- .../widgets/statistics-widget/statistics-widget.component.ts | 4 ++++ .../widgets/widget-frame/widget-frame.component.html | 4 ++++ .../dashboard/widgets/widget-frame/widget-frame.component.ts | 3 +++ 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html index 01809d1c6..8d5b9c43f 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html @@ -1,4 +1,4 @@ - + Show all diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts index 9506e6842..6e8b67900 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts @@ -15,6 +15,8 @@ import { QueryParamsService } from 'src/app/services/query-params.service' styleUrls: ['./saved-view-widget.component.scss'], }) export class SavedViewWidgetComponent implements OnInit, OnDestroy { + loading: boolean = true + constructor( private documentService: DocumentService, private router: Router, @@ -43,6 +45,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy { } reload() { + this.loading = true this.documentService .listFiltered( 1, @@ -52,6 +55,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy { this.savedView.filter_rules ) .subscribe((result) => { + this.loading = false this.documents = result.results }) } diff --git a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html index a29b50f78..106d30610 100644 --- a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -1,4 +1,4 @@ - +

Documents in inbox: {{statistics?.documents_inbox}}

Total documents: {{statistics?.documents_total}}

diff --git a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts index a13839f19..3d9f7b5e7 100644 --- a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts @@ -15,6 +15,8 @@ export interface Statistics { styleUrls: ['./statistics-widget.component.scss'], }) export class StatisticsWidgetComponent implements OnInit, OnDestroy { + loading: boolean = true + constructor( private http: HttpClient, private consumerStatusService: ConsumerStatusService @@ -29,7 +31,9 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy { } reload() { + this.loading = true this.getStatistics().subscribe((statistics) => { + this.loading = false this.statistics = statistics }) } diff --git a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html index a9d3d306a..4eaee4761 100644 --- a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html +++ b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html @@ -2,6 +2,10 @@
{{title}}
+ +
+
Loading...
+
diff --git a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts index f21f6ca35..b1e926eef 100644 --- a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts @@ -11,5 +11,8 @@ export class WidgetFrameComponent implements OnInit { @Input() title: string + @Input() + loading: boolean = false + ngOnInit(): void {} } From 1e7404662e2f86cad59f04f2591a027786494a6e Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 8 May 2022 14:16:37 -0700 Subject: [PATCH 006/291] loading indicators for sidebar saved views --- .../src/app/components/app-frame/app-frame.component.html | 3 ++- .../src/app/components/app-frame/app-frame.component.scss | 5 +++++ src-ui/src/app/services/rest/saved-view.service.ts | 8 +++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index d90d3b2d9..8f480dea4 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -70,8 +70,9 @@ -
+ + + diff --git a/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.scss b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts new file mode 100644 index 000000000..2ec0821e6 --- /dev/null +++ b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core' +import { FormControl, FormGroup } from '@angular/forms' +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' +import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' +import { StoragePathService } from 'src/app/services/rest/storage-path.service' +import { ToastService } from 'src/app/services/toast.service' + +@Component({ + selector: 'app-storage-path-edit-dialog', + templateUrl: './storage-path-edit-dialog.component.html', + styleUrls: ['./storage-path-edit-dialog.component.scss'], +}) +export class StoragePathEditDialogComponent extends EditDialogComponent { + constructor( + service: StoragePathService, + activeModal: NgbActiveModal, + toastService: ToastService + ) { + super(service, activeModal, toastService) + } + + get pathHint() { + return ( + $localize`e.g.` + + ' {created_year}-{title} ' + + $localize`or use slashes to add directories e.g.` + + ' {created_year}/{correspondent}/{title}. ' + + $localize`See documentation for full list.` + ) + } + + getCreateTitle() { + return $localize`Create new storage path` + } + + getEditTitle() { + return $localize`Edit storage path` + } + + getForm(): FormGroup { + return new FormGroup({ + name: new FormControl(''), + path: new FormControl(''), + matching_algorithm: new FormControl(1), + match: new FormControl(''), + is_insensitive: new FormControl(true), + }) + } +} diff --git a/src-ui/src/app/components/common/input/select/select.component.html b/src-ui/src/app/components/common/input/select/select.component.html index afb551c46..b111e1656 100644 --- a/src-ui/src/app/components/common/input/select/select.component.html +++ b/src-ui/src/app/components/common/input/select/select.component.html @@ -9,7 +9,8 @@ [items]="items" [addTag]="allowCreateNew && addItemRef" addTagText="Add item" - i18n-addTagText="Used for both types and correspondents" + i18n-addTagText="Used for both types, correspondents, storage paths" + [placeholder]="placeholder" bindLabel="name" bindValue="id" (change)="onChange(value)" diff --git a/src-ui/src/app/components/common/input/select/select.component.ts b/src-ui/src/app/components/common/input/select/select.component.ts index 98aef8d94..d1a3d6e5c 100644 --- a/src-ui/src/app/components/common/input/select/select.component.ts +++ b/src-ui/src/app/components/common/input/select/select.component.ts @@ -41,6 +41,9 @@ export class SelectComponent extends AbstractInputComponent { @Input() suggestions: number[] + @Input() + placeholder: string + @Output() createNew = new EventEmitter() diff --git a/src-ui/src/app/components/common/input/text/text.component.html b/src-ui/src/app/components/common/input/text/text.component.html index 3f4136c11..41f02df64 100644 --- a/src-ui/src/app/components/common/input/text/text.component.html +++ b/src-ui/src/app/components/common/input/text/text.component.html @@ -1,7 +1,7 @@
- {{hint}} +
{{error}}
diff --git a/src-ui/src/app/components/common/toasts/toasts.component.scss b/src-ui/src/app/components/common/toasts/toasts.component.scss index d2aaf20b0..e6dcb7a57 100644 --- a/src-ui/src/app/components/common/toasts/toasts.component.scss +++ b/src-ui/src/app/components/common/toasts/toasts.component.scss @@ -8,4 +8,4 @@ .toast:not(.show) { display: block; // this corrects an ng-bootstrap bug that prevented animations -} \ No newline at end of file +} diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index a4203473f..0f355eb06 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -73,6 +73,8 @@ (createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents"> + diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index f76e91e4d..6728f746d 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -33,6 +33,9 @@ import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-su import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type' import { normalizeDateStr } from 'src/app/utils/date' import { QueryParamsService } from 'src/app/services/query-params.service' +import { StoragePathService } from 'src/app/services/rest/storage-path.service' +import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' +import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component' import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' @Component({ @@ -66,6 +69,7 @@ export class DocumentDetailComponent correspondents: PaperlessCorrespondent[] documentTypes: PaperlessDocumentType[] + storagePaths: PaperlessStoragePath[] documentForm: FormGroup = new FormGroup({ title: new FormControl(''), @@ -73,6 +77,7 @@ export class DocumentDetailComponent created: new FormControl(), correspondent: new FormControl(), document_type: new FormControl(), + storage_path: new FormControl(), archive_serial_number: new FormControl(), tags: new FormControl([]), }) @@ -115,6 +120,7 @@ export class DocumentDetailComponent private documentTitlePipe: DocumentTitlePipe, private toastService: ToastService, private settings: SettingsService, + private storagePathService: StoragePathService, private queryParamsService: QueryParamsService ) {} @@ -163,11 +169,17 @@ export class DocumentDetailComponent .listAll() .pipe(first()) .subscribe((result) => (this.correspondents = result.results)) + this.documentTypeService .listAll() .pipe(first()) .subscribe((result) => (this.documentTypes = result.results)) + this.storagePathService + .listAll() + .pipe(first()) + .subscribe((result) => (this.storagePaths = result.results)) + this.route.paramMap .pipe( takeUntil(this.unsubscribeNotifier), @@ -230,6 +242,7 @@ export class DocumentDetailComponent created: this.ogDate.toISOString(), correspondent: doc.correspondent, document_type: doc.document_type, + storage_path: doc.storage_path, archive_serial_number: doc.archive_serial_number, tags: [...doc.tags], }) @@ -336,6 +349,27 @@ export class DocumentDetailComponent }) } + createStoragePath(newName: string) { + var modal = this.modalService.open(StoragePathEditDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.dialogMode = 'create' + if (newName) modal.componentInstance.object = { name: newName } + modal.componentInstance.success + .pipe( + switchMap((newStoragePath) => { + return this.storagePathService + .listAll() + .pipe(map((storagePaths) => ({ newStoragePath, storagePaths }))) + }) + ) + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe(({ newStoragePath, documentTypes: storagePaths }) => { + this.storagePaths = storagePaths.results + this.documentForm.get('storage_path').setValue(newStoragePath.id) + }) + } + discard() { this.documentsService .get(this.documentId) diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html index 631b558d0..8d9389df3 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -53,6 +53,15 @@ [(selectionModel)]="documentTypeSelectionModel" (apply)="setDocumentTypes($event)"> + +
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts index b17af67eb..b9ab00fe6 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -22,6 +22,8 @@ import { MatchingModel } from 'src/app/data/matching-model' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' import { saveAs } from 'file-saver' +import { StoragePathService } from 'src/app/services/rest/storage-path.service' +import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' @Component({ @@ -33,10 +35,12 @@ export class BulkEditorComponent { tags: PaperlessTag[] correspondents: PaperlessCorrespondent[] documentTypes: PaperlessDocumentType[] + storagePaths: PaperlessStoragePath[] tagSelectionModel = new FilterableDropdownSelectionModel() correspondentSelectionModel = new FilterableDropdownSelectionModel() documentTypeSelectionModel = new FilterableDropdownSelectionModel() + storagePathsSelectionModel = new FilterableDropdownSelectionModel() awaitingDownload: boolean constructor( @@ -48,7 +52,8 @@ export class BulkEditorComponent { private modalService: NgbModal, private openDocumentService: OpenDocumentsService, private settings: SettingsService, - private toastService: ToastService + private toastService: ToastService, + private storagePathService: StoragePathService ) {} applyOnClose: boolean = this.settings.get( @@ -68,6 +73,9 @@ export class BulkEditorComponent { this.documentTypeService .listAll() .subscribe((result) => (this.documentTypes = result.results)) + this.storagePathService + .listAll() + .subscribe((result) => (this.storagePaths = result.results)) } private executeBulkOperation(modal, method: string, args) { @@ -145,6 +153,17 @@ export class BulkEditorComponent { }) } + openStoragePathDropdown() { + this.documentService + .getSelectionData(Array.from(this.list.selected)) + .subscribe((s) => { + this.applySelectionData( + s.selected_storage_paths, + this.storagePathsSelectionModel + ) + }) + } + private _localizeList(items: MatchingModel[]) { if (items.length == 0) { return '' @@ -299,6 +318,42 @@ export class BulkEditorComponent { } } + setStoragePaths(changedDocumentPaths: ChangedItems) { + if ( + changedDocumentPaths.itemsToAdd.length == 0 && + changedDocumentPaths.itemsToRemove.length == 0 + ) + return + + let storagePath = + changedDocumentPaths.itemsToAdd.length > 0 + ? changedDocumentPaths.itemsToAdd[0] + : null + + if (this.showConfirmationDialogs) { + let modal = this.modalService.open(ConfirmDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.title = $localize`Confirm storage path assignment` + if (storagePath) { + modal.componentInstance.message = $localize`This operation will assign the storage path "${storagePath.name}" to ${this.list.selected.size} selected document(s).` + } else { + modal.componentInstance.message = $localize`This operation will remove the storage path from ${this.list.selected.size} selected document(s).` + } + modal.componentInstance.btnClass = 'btn-warning' + modal.componentInstance.btnCaption = $localize`Confirm` + modal.componentInstance.confirmClicked.subscribe(() => { + this.executeBulkOperation(modal, 'set_storage_path', { + storage_path: storagePath ? storagePath.id : null, + }) + }) + } else { + this.executeBulkOperation(null, 'set_storage_path', { + storage_path: storagePath ? storagePath.id : null, + }) + } + } + applyDelete() { let modal = this.modalService.open(ConfirmDialogComponent, { backdrop: 'static', diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html index 5eb4a97dd..cbc507de4 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html @@ -67,6 +67,13 @@ {{(document.document_type$ | async)?.name}} +
{{(document.document_type$ | async)?.name}} +
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts index 06e2fe967..3f7fc7982 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts @@ -47,6 +47,9 @@ export class DocumentCardSmallComponent implements OnInit { @Output() clickDocumentType = new EventEmitter() + @Output() + clickStoragePath = new EventEmitter() + moreTags: number = null @ViewChild('popover') popover: NgbPopover diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index aa43243fc..d0aaf5890 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -107,7 +107,7 @@
- +
@@ -138,6 +138,12 @@ [currentSortReverse]="list.sortReverse" (sort)="onSort($event)" i18n>Document type + Storage path {{(d.document_type$ | async)?.name}} + + + {{(d.storage_path$ | async)?.name}} + + {{d.created | customDate}} @@ -187,7 +198,7 @@
- +
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 8cf6c3848..d9355902f 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -265,6 +265,13 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit { }) } + clickStoragePath(storagePathID: number) { + this.list.selectNone() + setTimeout(() => { + this.filterEditor.addStoragePath(storagePathID) + }) + } + clickMoreLike(documentID: number) { this.queryParamsService.navigateWithFilterRules([ { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() }, diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html index 40c62d779..b02d6cc7a 100644 --- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html +++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html @@ -1,7 +1,7 @@ -
+
-
+
- +
+ {{moreTags}}
@@ -21,21 +21,21 @@ - -
-