From 77913c2deacb62e23ddf47bbe8eb8d69f0afdddf Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 13 Aug 2023 23:39:14 -0700 Subject: [PATCH] Implement share links Basic implementation of share links Make certain share link fields not editable, automatically grant permissions on migrate Updated styling, error messages from expired / deleted links frontend code linting, reversable sharelink migration testing coverage Update translation strings No links message --- src-ui/messages.xlf | 360 +++++++---- src-ui/setup-jest.ts | 1 + src-ui/src/app/app.module.ts | 2 + ...mail-account-edit-dialog.component.spec.ts | 1 - .../share-links-dropdown.component.html | 61 ++ .../share-links-dropdown.component.scss | 7 + .../share-links-dropdown.component.spec.ts | 192 ++++++ .../share-links-dropdown.component.ts | 142 +++++ .../document-detail.component.html | 39 +- .../document-detail.component.spec.ts | 2 + .../document-detail.component.ts | 1 + src-ui/src/app/data/paperless-share-link.ts | 18 + .../app/services/permissions.service.spec.ts | 4 + .../src/app/services/permissions.service.ts | 1 + .../rest/abstract-paperless-service.ts | 2 +- .../services/rest/share-link.service.spec.ts | 42 ++ .../app/services/rest/share-link.service.ts | 36 ++ src/documents/admin.py | 8 + src/documents/filters.py | 10 + src/documents/migrations/1038_sharelink.py | 126 ++++ src/documents/models.py | 61 ++ src/documents/serialisers.py | 19 + .../templates/registration/logged_out.html | 2 +- .../templates/registration/login.html | 16 +- src/documents/tests/test_api.py | 115 ++++ .../tests/test_management_exporter.py | 16 +- src/documents/tests/test_views.py | 75 +++ src/documents/views.py | 114 ++++ src/locale/en_US/LC_MESSAGES/django.po | 597 ++++++++++-------- src/paperless/urls.py | 6 +- 30 files changed, 1675 insertions(+), 401 deletions(-) create mode 100644 src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.html create mode 100644 src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.scss create mode 100644 src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.spec.ts create mode 100644 src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts create mode 100644 src-ui/src/app/data/paperless-share-link.ts create mode 100644 src-ui/src/app/services/rest/share-link.service.spec.ts create mode 100644 src-ui/src/app/services/rest/share-link.service.ts create mode 100644 src/documents/migrations/1038_sharelink.py diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index e7c04174b..ac2d9fe6a 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -319,7 +319,7 @@ src/app/components/document-detail/document-detail.component.html - 55 + 66 @@ -1073,7 +1073,7 @@ src/app/components/document-detail/document-detail.component.html - 198 + 209 src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html @@ -1142,7 +1142,7 @@ src/app/components/document-detail/document-detail.component.html - 182 + 193 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -1549,6 +1549,10 @@ src/app/components/common/permissions-select/permissions-select.component.html 9 + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 33 + src/app/components/document-detail/document-detail.component.html 11 @@ -2240,6 +2244,135 @@ 20 + + Share Links + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 6 + + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 25 + + + + No existing links + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 10,12 + + + + Copy + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 23 + + + + Share + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 28 + + + + Copied! + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 36 + + + + Share archive version + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 42 + + + + Create + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 55 + + + src/app/components/manage/management-list/management-list.component.html + 2 + + + src/app/components/manage/management-list/management-list.component.html + 2 + + + src/app/components/manage/management-list/management-list.component.html + 2 + + + src/app/components/manage/management-list/management-list.component.html + 2 + + + + 1 day + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 18 + + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 85 + + + + 7 days + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 19 + + + + 30 days + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 20 + + + + Never + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 21 + + + + Error retrieving links + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 69 + + + + days + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 85 + + + + Error deleting link + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 110 + + + + Error creating link + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts + 138 + + Status @@ -2310,7 +2443,7 @@ src/app/components/document-detail/document-detail.component.html - 75 + 86 src/app/components/document-list/document-list.component.html @@ -2340,7 +2473,7 @@ src/app/components/document-detail/document-detail.component.html - 19 + 18 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -2538,14 +2671,65 @@ Download original src/app/components/document-detail/document-detail.component.html - 25 + 24 + + + + Actions + + src/app/components/document-detail/document-detail.component.html + 37 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.html + 86 + + + src/app/components/manage/management-list/management-list.component.html + 23 + + + src/app/components/manage/management-list/management-list.component.html + 23 + + + src/app/components/manage/management-list/management-list.component.html + 23 + + + src/app/components/manage/management-list/management-list.component.html + 23 + + + src/app/components/manage/settings/settings.component.html + 221 + + + src/app/components/manage/settings/settings.component.html + 259 + + + src/app/components/manage/settings/settings.component.html + 296 + + + src/app/components/manage/settings/settings.component.html + 347 + + + src/app/components/manage/settings/settings.component.html + 382 + + + src/app/components/manage/tasks/tasks.component.html + 44 Redo OCR src/app/components/document-detail/document-detail.component.html - 34 + 43 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -2556,7 +2740,7 @@ More like this src/app/components/document-detail/document-detail.component.html - 40 + 49 src/app/components/document-list/document-card-large/document-card-large.component.html @@ -2567,7 +2751,7 @@ Close src/app/components/document-detail/document-detail.component.html - 43 + 54 src/app/guards/dirty-saved-view.guard.ts @@ -2578,35 +2762,35 @@ Previous src/app/components/document-detail/document-detail.component.html - 50 + 61 Details src/app/components/document-detail/document-detail.component.html - 72 + 83 Archive serial number src/app/components/document-detail/document-detail.component.html - 76 + 87 Date created src/app/components/document-detail/document-detail.component.html - 77 + 88 Correspondent src/app/components/document-detail/document-detail.component.html - 79 + 90 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -2629,7 +2813,7 @@ Document type src/app/components/document-detail/document-detail.component.html - 81 + 92 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -2652,7 +2836,7 @@ Storage path src/app/components/document-detail/document-detail.component.html - 83 + 94 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -2671,21 +2855,21 @@ Default src/app/components/document-detail/document-detail.component.html - 84 + 95 Content src/app/components/document-detail/document-detail.component.html - 91 + 102 Metadata src/app/components/document-detail/document-detail.component.html - 100 + 111 src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts @@ -2696,173 +2880,173 @@ Date modified src/app/components/document-detail/document-detail.component.html - 106 + 117 Date added src/app/components/document-detail/document-detail.component.html - 110 + 121 Media filename src/app/components/document-detail/document-detail.component.html - 114 + 125 Original filename src/app/components/document-detail/document-detail.component.html - 118 + 129 Original MD5 checksum src/app/components/document-detail/document-detail.component.html - 122 + 133 Original file size src/app/components/document-detail/document-detail.component.html - 126 + 137 Original mime type src/app/components/document-detail/document-detail.component.html - 130 + 141 Archive MD5 checksum src/app/components/document-detail/document-detail.component.html - 134 + 145 Archive file size src/app/components/document-detail/document-detail.component.html - 138 + 149 Original document metadata src/app/components/document-detail/document-detail.component.html - 144 + 155 Archived document metadata src/app/components/document-detail/document-detail.component.html - 145 + 156 Preview src/app/components/document-detail/document-detail.component.html - 151 + 162 Enter Password src/app/components/document-detail/document-detail.component.html - 167 + 178 src/app/components/document-detail/document-detail.component.html - 218 + 229 Notes src/app/components/document-detail/document-detail.component.html - 175,176 + 186,187 Discard src/app/components/document-detail/document-detail.component.html - 194 + 205 Save & next src/app/components/document-detail/document-detail.component.html - 196 + 207 Save & close src/app/components/document-detail/document-detail.component.html - 197 + 208 An error occurred loading content: src/app/components/document-detail/document-detail.component.ts - 252,254 + 253,255 Error retrieving metadata src/app/components/document-detail/document-detail.component.ts - 397 + 398 Error retrieving suggestions. src/app/components/document-detail/document-detail.component.ts - 418 + 419 Document saved successfully. src/app/components/document-detail/document-detail.component.ts - 531 + 532 src/app/components/document-detail/document-detail.component.ts - 539 + 540 Error saving document src/app/components/document-detail/document-detail.component.ts - 543 + 544 src/app/components/document-detail/document-detail.component.ts - 584 + 585 Confirm delete src/app/components/document-detail/document-detail.component.ts - 610 + 611 src/app/components/manage/management-list/management-list.component.ts @@ -2873,35 +3057,35 @@ Do you really want to delete document ""? src/app/components/document-detail/document-detail.component.ts - 611 + 612 The files for this document will be deleted permanently. This operation cannot be undone. src/app/components/document-detail/document-detail.component.ts - 612 + 613 Delete document src/app/components/document-detail/document-detail.component.ts - 614 + 615 Error deleting document src/app/components/document-detail/document-detail.component.ts - 633 + 634 Redo OCR confirm src/app/components/document-detail/document-detail.component.ts - 653 + 654 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -2912,14 +3096,14 @@ This operation will permanently redo OCR for this document. src/app/components/document-detail/document-detail.component.ts - 654 + 655 This operation cannot be undone. src/app/components/document-detail/document-detail.component.ts - 655 + 656 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -2950,7 +3134,7 @@ Proceed src/app/components/document-detail/document-detail.component.ts - 657 + 658 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -2977,14 +3161,14 @@ Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. src/app/components/document-detail/document-detail.component.ts - 665 + 666 Error executing operation src/app/components/document-detail/document-detail.component.ts - 676 + 677 @@ -3045,53 +3229,6 @@ 52 - - Actions - - src/app/components/document-list/bulk-editor/bulk-editor.component.html - 86 - - - src/app/components/manage/management-list/management-list.component.html - 23 - - - src/app/components/manage/management-list/management-list.component.html - 23 - - - src/app/components/manage/management-list/management-list.component.html - 23 - - - src/app/components/manage/management-list/management-list.component.html - 23 - - - src/app/components/manage/settings/settings.component.html - 221 - - - src/app/components/manage/settings/settings.component.html - 259 - - - src/app/components/manage/settings/settings.component.html - 296 - - - src/app/components/manage/settings/settings.component.html - 347 - - - src/app/components/manage/settings/settings.component.html - 382 - - - src/app/components/manage/tasks/tasks.component.html - 44 - - Include: @@ -3945,25 +4082,6 @@ 44 - - Create - - src/app/components/manage/management-list/management-list.component.html - 2 - - - src/app/components/manage/management-list/management-list.component.html - 2 - - - src/app/components/manage/management-list/management-list.component.html - 2 - - - src/app/components/manage/management-list/management-list.component.html - 2 - - Filter by: diff --git a/src-ui/setup-jest.ts b/src-ui/setup-jest.ts index 65004742b..c0dfad9f9 100644 --- a/src-ui/setup-jest.ts +++ b/src-ui/setup-jest.ts @@ -86,6 +86,7 @@ Object.defineProperty(navigator, 'clipboard', { writeText: async () => {}, }, }) +Object.defineProperty(navigator, 'canShare', { value: () => true }) Object.defineProperty(window, 'ResizeObserver', { value: mock() }) HTMLCanvasElement.prototype.getContext = < diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index aac7a5238..f46c06cb9 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -94,6 +94,7 @@ import { PermissionsFilterDropdownComponent } from './components/common/permissi import { UsernamePipe } from './pipes/username.pipe' import { LogoComponent } from './components/common/logo/logo.component' import { IsNumberPipe } from './pipes/is-number.pipe' +import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component' import localeAf from '@angular/common/locales/af' import localeAr from '@angular/common/locales/ar' @@ -231,6 +232,7 @@ function initializeApp(settings: SettingsService) { UsernamePipe, LogoComponent, IsNumberPipe, + ShareLinksDropdownComponent, ], imports: [ BrowserModule, diff --git a/src-ui/src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.spec.ts index 93fa7f0fd..1a35fb5ef 100644 --- a/src-ui/src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.spec.ts +++ b/src-ui/src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed, - discardPeriodicTasks, fakeAsync, tick, } from '@angular/core/testing' diff --git a/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.html b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.html new file mode 100644 index 000000000..e4e24bb22 --- /dev/null +++ b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.html @@ -0,0 +1,61 @@ +
+ + +
diff --git a/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.scss b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.scss new file mode 100644 index 000000000..26797bad0 --- /dev/null +++ b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.scss @@ -0,0 +1,7 @@ +.share-links-dropdown { + min-width: 350px; +} + +.copied-badge { + right: 7.5em; +} diff --git a/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.spec.ts b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.spec.ts new file mode 100644 index 000000000..55f544815 --- /dev/null +++ b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.spec.ts @@ -0,0 +1,192 @@ +import { + HttpTestingController, + HttpClientTestingModule, +} from '@angular/common/http/testing' +import { + ComponentFixture, + TestBed, + fakeAsync, + tick, +} from '@angular/core/testing' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { of, throwError } from 'rxjs' +import { + PaperlessShareLinkDocumentVersion, + PaperlessShareLink, +} from 'src/app/data/paperless-share-link' +import { ShareLinkService } from 'src/app/services/rest/share-link.service' +import { ToastService } from 'src/app/services/toast.service' +import { environment } from 'src/environments/environment' +import { ShareLinksDropdownComponent } from './share-links-dropdown.component' + +describe('ShareLinksDropdownComponent', () => { + let component: ShareLinksDropdownComponent + let fixture: ComponentFixture + let shareLinkService: ShareLinkService + let toastService: ToastService + let httpController: HttpTestingController + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ShareLinksDropdownComponent], + imports: [HttpClientTestingModule, FormsModule, ReactiveFormsModule], + }) + + fixture = TestBed.createComponent(ShareLinksDropdownComponent) + shareLinkService = TestBed.inject(ShareLinkService) + toastService = TestBed.inject(ToastService) + httpController = TestBed.inject(HttpTestingController) + + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should support refresh to retrieve links', () => { + const getSpy = jest.spyOn(shareLinkService, 'getLinksForDocument') + component.documentId = 99 + + const now = new Date() + const expiration7days = new Date() + expiration7days.setDate(now.getDate() + 7) + + getSpy.mockReturnValue( + of([ + { + id: 1, + slug: '1234slug', + created: now.toISOString(), + document: 99, + document_version: PaperlessShareLinkDocumentVersion.Archive, + expiration: expiration7days.toISOString(), + }, + { + id: 1, + slug: '1234slug', + created: now.toISOString(), + document: 99, + document_version: PaperlessShareLinkDocumentVersion.Original, + expiration: null, + }, + ]) + ) + + component.refresh() + expect(getSpy).toHaveBeenCalled() + + fixture.detectChanges() + + expect(component.shareLinks).toHaveLength(2) + }) + + it('should show error on refresh if needed', () => { + const toastSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(shareLinkService, 'getLinksForDocument') + .mockReturnValueOnce(throwError(() => new Error('Unable to get links'))) + component.documentId = 99 + + component.refresh() + fixture.detectChanges() + expect(toastSpy).toHaveBeenCalled() + }) + + it('should support link creation then refresh & copy url', fakeAsync(() => { + const createSpy = jest.spyOn(shareLinkService, 'createLinkForDocument') + component.documentId = 99 + component.expirationDays = 7 + component.archiveVersion = false + + const expiration = new Date() + expiration.setDate(expiration.getDate() + 7) + + const copySpy = jest.spyOn(navigator.clipboard, 'writeText') + const refreshSpy = jest.spyOn(component, 'refresh') + + component.createLink() + expect(createSpy).toHaveBeenCalledWith(99, 'original', expiration) + + httpController.expectOne(`${environment.apiBaseUrl}share_links/`).flush({ + id: 1, + slug: '1234slug', + document: 99, + expiration: expiration.toISOString(), + }) + fixture.detectChanges() + tick(3000) + + expect(copySpy).toHaveBeenCalled() + expect(refreshSpy).toHaveBeenCalled() + })) + + it('should show error on link creation if needed', () => { + component.documentId = 99 + component.expirationDays = 7 + + const expiration = new Date() + expiration.setDate(expiration.getDate() + 7) + + const toastSpy = jest.spyOn(toastService, 'showError') + + component.createLink() + + httpController + .expectOne(`${environment.apiBaseUrl}share_links/`) + .flush( + { error: 'Share link error' }, + { status: 500, statusText: 'error' } + ) + fixture.detectChanges() + + expect(toastSpy).toHaveBeenCalled() + }) + + it('should support delete links & refresh', () => { + const deleteSpy = jest.spyOn(shareLinkService, 'delete') + deleteSpy.mockReturnValue(of(true)) + const refreshSpy = jest.spyOn(component, 'refresh') + + component.delete({ id: 12 } as PaperlessShareLink) + fixture.detectChanges() + expect(deleteSpy).toHaveBeenCalledWith({ id: 12 }) + expect(refreshSpy).toHaveBeenCalled() + }) + + it('should show error on delete if needed', () => { + const toastSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(shareLinkService, 'delete') + .mockReturnValueOnce(throwError(() => new Error('Unable to delete link'))) + component.delete(null) + fixture.detectChanges() + expect(toastSpy).toHaveBeenCalled() + }) + + it('should format days remaining', () => { + const now = new Date() + const expiration7days = new Date() + expiration7days.setDate(now.getDate() + 7) + const expiration1day = new Date() + expiration1day.setDate(now.getDate() + 1) + + expect( + component.getDaysRemaining({ + expiration: expiration7days.toISOString(), + } as PaperlessShareLink) + ).toEqual('7 days') + expect( + component.getDaysRemaining({ + expiration: expiration1day.toISOString(), + } as PaperlessShareLink) + ).toEqual('1 day') + }) + + // coverage + it('should support share', () => { + const link = { slug: '12345slug' } as PaperlessShareLink + if (!('share' in navigator)) + Object.defineProperty(navigator, 'share', { value: (obj: any) => {} }) + // const navigatorSpy = jest.spyOn(navigator, 'share') + component.share(link) + // expect(navigatorSpy).toHaveBeenCalledWith({ url: component.getShareUrl(link) }) + }) +}) diff --git a/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts new file mode 100644 index 000000000..972551d2b --- /dev/null +++ b/src-ui/src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts @@ -0,0 +1,142 @@ +import { Component, Input, OnInit } from '@angular/core' +import { first } from 'rxjs' +import { + PaperlessShareLink, + PaperlessShareLinkDocumentVersion, +} from 'src/app/data/paperless-share-link' +import { ShareLinkService } from 'src/app/services/rest/share-link.service' +import { ToastService } from 'src/app/services/toast.service' +import { environment } from 'src/environments/environment' + +@Component({ + selector: 'app-share-links-dropdown', + templateUrl: './share-links-dropdown.component.html', + styleUrls: ['./share-links-dropdown.component.scss'], +}) +export class ShareLinksDropdownComponent implements OnInit { + EXPIRATION_OPTIONS = [ + { label: $localize`1 day`, value: 1 }, + { label: $localize`7 days`, value: 7 }, + { label: $localize`30 days`, value: 30 }, + { label: $localize`Never`, value: null }, + ] + + @Input() + title = $localize`Share Links` + + _documentId: number + + @Input() + set documentId(id: number) { + this._documentId = id + this.refresh() + } + + shareLinks: PaperlessShareLink[] + + buttonsEnabled: boolean = true + + loading: boolean = false + + copied: number + + expirationDays: number = 7 + + archiveVersion: boolean = true + + constructor( + private shareLinkService: ShareLinkService, + private toastService: ToastService + ) {} + + ngOnInit(): void { + this.refresh() + } + + refresh() { + if (!this._documentId) return + this.loading = true + this.shareLinkService + .getLinksForDocument(this._documentId) + .pipe(first()) + .subscribe({ + next: (results) => { + this.loading = false + this.shareLinks = results + }, + error: (e) => { + this.toastService.showError( + $localize`Error retrieving links`, + 10000, + e + ) + }, + }) + } + + getShareUrl(link: PaperlessShareLink): string { + return `${environment.apiBaseUrl.replace('api', 'share')}${link.slug}` + } + + getDaysRemaining(link: PaperlessShareLink): string { + const days: number = Math.ceil( + (Date.parse(link.expiration) - Date.now()) / (1000 * 60 * 60 * 24) + ) + return days === 1 ? $localize`1 day` : $localize`${days} days` + } + + copy(link: PaperlessShareLink) { + navigator.clipboard.writeText(this.getShareUrl(link)) + this.copied = link.id + setTimeout(() => { + this.copied = null + }, 3000) + } + + canShare(): boolean { + return navigator && 'canShare' in navigator && navigator.canShare() + } + + share(link: PaperlessShareLink) { + navigator.share({ url: this.getShareUrl(link) }) + } + + delete(link: PaperlessShareLink) { + this.shareLinkService.delete(link).subscribe({ + next: () => { + this.refresh() + }, + error: (e) => { + this.toastService.showError($localize`Error deleting link`, 10000, e) + }, + }) + } + + createLink() { + let expiration + if (this.expirationDays) { + expiration = new Date() + expiration.setDate(expiration.getDate() + this.expirationDays) + } + this.loading = true + this.shareLinkService + .createLinkForDocument( + this._documentId, + this.archiveVersion + ? PaperlessShareLinkDocumentVersion.Archive + : PaperlessShareLinkDocumentVersion.Original, + expiration + ) + .subscribe({ + next: (result) => { + this.loading = false + this.copy(result) + this.refresh() + }, + error: (e) => { + this.loading = false + this.toastService.showError($localize`Error creating link`, 10000, e) + }, + }) + } +} 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 41e7a78d1..11237cce9 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 @@ -5,16 +5,15 @@
of {{previewNumPages}}
-
- - + Download @@ -25,20 +24,32 @@ Download original
- - - + + +
+ +
+ + + +
+