mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Feature: loading preview, better text popup preview (#8011)
This commit is contained in:
		| @@ -118,17 +118,6 @@ | |||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <div class="row mb-3"> |  | ||||||
|           <div class="col-md-3 col-form-label pt-0"> |  | ||||||
|             <span i18n>Document editor</span> |  | ||||||
|           </div> |  | ||||||
|           <div class="col"> |  | ||||||
|  |  | ||||||
|             <pngx-input-check i18n-title title="Use PDF viewer provided by the browser" i18n-hint hint="This is usually faster for displaying large PDF documents, but it might not work on some browsers." formControlName="useNativePdfViewer"></pngx-input-check> |  | ||||||
|  |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         <div class="row mb-3"> |         <div class="row mb-3"> | ||||||
|           <div class="col-md-3 col-form-label pt-0"> |           <div class="col-md-3 col-form-label pt-0"> | ||||||
|             <span i18n>Sidebar</span> |             <span i18n>Sidebar</span> | ||||||
| @@ -168,26 +157,42 @@ | |||||||
|         <h4 class="mt-4" id="update-checking" i18n>Update checking</h4> |         <h4 class="mt-4" id="update-checking" i18n>Update checking</h4> | ||||||
|  |  | ||||||
|         <div class="row mb-3"> |         <div class="row mb-3"> | ||||||
|           <div class="offset-md-3 col"> |           <div class="offset-md-3 col d-flex flex-row align-items-start"> | ||||||
|             <p i18n> |  | ||||||
|             Update checking works by pinging the public <a href="https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest" target="_blank" rel="noopener noreferrer">GitHub API</a> for the latest release to determine whether a new version is available.<br/> |  | ||||||
|             Actual updating of the app must still be performed manually. |  | ||||||
|           </p> |  | ||||||
|             <p i18n> |  | ||||||
|             <em>No tracking data is collected by the app in any way.</em> |  | ||||||
|           </p> |  | ||||||
|             <pngx-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled"></pngx-input-check> |             <pngx-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled"></pngx-input-check> | ||||||
|  |             <button class="btn btn-sm btn-link text-muted me-auto p-0 ms-2" title="What's this?" i18n-title type="button" triggers="mouseenter:mouseleave" [ngbPopover]="updatesPopover" [autoClose]="true"> | ||||||
|  |               <i-bs name="question-circle"></i-bs> | ||||||
|  |             </button> | ||||||
|  |             <ng-template #updatesPopover> | ||||||
|  |               <p i18n> | ||||||
|  |                 Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. | ||||||
|  |               </p> | ||||||
|  |               <p> | ||||||
|  |                 <em i18n>No tracking data is collected by the app in any way.</em> | ||||||
|  |               </p> | ||||||
|  |             </ng-template> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <h4 class="mt-4" i18n>Document editing</h4> |         <h4 class="mt-4" i18n>Document editing</h4> | ||||||
|  |  | ||||||
|  |         <div class="row mb-3"> | ||||||
|  |           <div class="offset-md-3 col"> | ||||||
|  |             <pngx-input-check i18n-title title="Use PDF viewer provided by the browser" i18n-hint hint="This is usually faster for displaying large PDF documents, but it might not work on some browsers." formControlName="useNativePdfViewer"></pngx-input-check> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|         <div class="row mb-3"> |         <div class="row mb-3"> | ||||||
|           <div class="offset-md-3 col"> |           <div class="offset-md-3 col"> | ||||||
|             <pngx-input-check i18n-title title="Automatically remove inbox tag(s) on save" formControlName="documentEditingRemoveInboxTags"></pngx-input-check> |             <pngx-input-check i18n-title title="Automatically remove inbox tag(s) on save" formControlName="documentEditingRemoveInboxTags"></pngx-input-check> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  |         <div class="row mb-3"> | ||||||
|  |           <div class="offset-md-3 col"> | ||||||
|  |             <pngx-input-check i18n-title title="Show document thumbnail during loading" formControlName="documentEditingOverlayThumbnail"></pngx-input-check> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|         <h4 class="mt-4" i18n>Bulk editing</h4> |         <h4 class="mt-4" i18n>Bulk editing</h4> | ||||||
|  |  | ||||||
|         <div class="row mb-3"> |         <div class="row mb-3"> | ||||||
|   | |||||||
| @@ -315,7 +315,7 @@ describe('SettingsComponent', () => { | |||||||
|     expect(toastErrorSpy).toHaveBeenCalled() |     expect(toastErrorSpy).toHaveBeenCalled() | ||||||
|     expect(storeSpy).toHaveBeenCalled() |     expect(storeSpy).toHaveBeenCalled() | ||||||
|     expect(appearanceSettingsSpy).not.toHaveBeenCalled() |     expect(appearanceSettingsSpy).not.toHaveBeenCalled() | ||||||
|     expect(setSpy).toHaveBeenCalledTimes(27) |     expect(setSpy).toHaveBeenCalledTimes(28) | ||||||
|  |  | ||||||
|     // succeed |     // succeed | ||||||
|     storeSpy.mockReturnValueOnce(of(true)) |     storeSpy.mockReturnValueOnce(of(true)) | ||||||
|   | |||||||
| @@ -88,7 +88,6 @@ export class SettingsComponent | |||||||
|     darkModeEnabled: new FormControl(null), |     darkModeEnabled: new FormControl(null), | ||||||
|     darkModeInvertThumbs: new FormControl(null), |     darkModeInvertThumbs: new FormControl(null), | ||||||
|     themeColor: new FormControl(null), |     themeColor: new FormControl(null), | ||||||
|     useNativePdfViewer: new FormControl(null), |  | ||||||
|     displayLanguage: new FormControl(null), |     displayLanguage: new FormControl(null), | ||||||
|     dateLocale: new FormControl(null), |     dateLocale: new FormControl(null), | ||||||
|     dateFormat: new FormControl(null), |     dateFormat: new FormControl(null), | ||||||
| @@ -99,7 +98,9 @@ export class SettingsComponent | |||||||
|     defaultPermsViewGroups: new FormControl(null), |     defaultPermsViewGroups: new FormControl(null), | ||||||
|     defaultPermsEditUsers: new FormControl(null), |     defaultPermsEditUsers: new FormControl(null), | ||||||
|     defaultPermsEditGroups: new FormControl(null), |     defaultPermsEditGroups: new FormControl(null), | ||||||
|  |     useNativePdfViewer: new FormControl(null), | ||||||
|     documentEditingRemoveInboxTags: new FormControl(null), |     documentEditingRemoveInboxTags: new FormControl(null), | ||||||
|  |     documentEditingOverlayThumbnail: new FormControl(null), | ||||||
|     searchDbOnly: new FormControl(null), |     searchDbOnly: new FormControl(null), | ||||||
|     searchLink: new FormControl(null), |     searchLink: new FormControl(null), | ||||||
|  |  | ||||||
| @@ -308,6 +309,9 @@ export class SettingsComponent | |||||||
|       documentEditingRemoveInboxTags: this.settings.get( |       documentEditingRemoveInboxTags: this.settings.get( | ||||||
|         SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS |         SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS | ||||||
|       ), |       ), | ||||||
|  |       documentEditingOverlayThumbnail: this.settings.get( | ||||||
|  |         SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL | ||||||
|  |       ), | ||||||
|       searchDbOnly: this.settings.get(SETTINGS_KEYS.SEARCH_DB_ONLY), |       searchDbOnly: this.settings.get(SETTINGS_KEYS.SEARCH_DB_ONLY), | ||||||
|       searchLink: this.settings.get(SETTINGS_KEYS.SEARCH_FULL_TYPE), |       searchLink: this.settings.get(SETTINGS_KEYS.SEARCH_FULL_TYPE), | ||||||
|       savedViews: {}, |       savedViews: {}, | ||||||
| @@ -539,6 +543,10 @@ export class SettingsComponent | |||||||
|       SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS, |       SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS, | ||||||
|       this.settingsForm.value.documentEditingRemoveInboxTags |       this.settingsForm.value.documentEditingRemoveInboxTags | ||||||
|     ) |     ) | ||||||
|  |     this.settings.set( | ||||||
|  |       SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL, | ||||||
|  |       this.settingsForm.value.documentEditingOverlayThumbnail | ||||||
|  |     ) | ||||||
|     this.settings.set( |     this.settings.set( | ||||||
|       SETTINGS_KEYS.SEARCH_DB_ONLY, |       SETTINGS_KEYS.SEARCH_DB_ONLY, | ||||||
|       this.settingsForm.value.searchDbOnly |       this.settingsForm.value.searchDbOnly | ||||||
|   | |||||||
| @@ -5,7 +5,11 @@ | |||||||
|     </div> |     </div> | ||||||
|   } @else { |   } @else { | ||||||
|     @if (renderAsObject) { |     @if (renderAsObject) { | ||||||
|       <object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf" [class.pdf]="isPdf"></object> |       @if (previewText) { | ||||||
|  |         <div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div> | ||||||
|  |       } @else { | ||||||
|  |         <object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object> | ||||||
|  |       } | ||||||
|     } @else { |     } @else { | ||||||
|       @if (requiresPassword) { |       @if (requiresPassword) { | ||||||
|         <div class="w-100 h-100 position-relative"> |         <div class="w-100 h-100 position-relative"> | ||||||
|   | |||||||
| @@ -7,33 +7,3 @@ | |||||||
| ::ng-deep .popover.popover-preview { | ::ng-deep .popover.popover-preview { | ||||||
|     max-width: 32rem; |     max-width: 32rem; | ||||||
| } | } | ||||||
|  |  | ||||||
| // https://github.com/paperless-ngx/paperless-ngx/issues/7920 |  | ||||||
| // TODO: remove me |  | ||||||
| @mixin ff_txt { |  | ||||||
|   .preview-popup-container { |  | ||||||
|     width: 30rem !important; |  | ||||||
|     height: 22rem !important; |  | ||||||
|     background-color: #e7e7e7; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   object:not(.pdf) { |  | ||||||
|     mix-blend-mode: difference; |  | ||||||
|     background: white !important; |  | ||||||
|     &.p-2 { |  | ||||||
|       padding: 0 !important; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @-moz-document url-prefix() { |  | ||||||
|   html[data-bs-theme='dark'] { |  | ||||||
|     @include ff_txt; |  | ||||||
|   } |  | ||||||
|   html[data-bs-theme='auto'] { |  | ||||||
|     @media screen and (prefers-color-scheme: dark) { |  | ||||||
|       @include ff_txt; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -9,13 +9,20 @@ import { provideHttpClientTesting } from '@angular/common/http/testing' | |||||||
| import { DocumentService } from 'src/app/services/rest/document.service' | import { DocumentService } from 'src/app/services/rest/document.service' | ||||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||||
| import { PdfViewerModule } from 'ng2-pdf-viewer' | import { PdfViewerModule } from 'ng2-pdf-viewer' | ||||||
| import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' | import { | ||||||
|  |   HttpClient, | ||||||
|  |   provideHttpClient, | ||||||
|  |   withInterceptorsFromDi, | ||||||
|  | } from '@angular/common/http' | ||||||
|  | import { of, throwError } from 'rxjs' | ||||||
|  |  | ||||||
| const doc = { | const doc = { | ||||||
|   id: 10, |   id: 10, | ||||||
|   title: 'Document 10', |   title: 'Document 10', | ||||||
|   content: 'Cupcake ipsum dolor sit amet ice cream.', |   content: 'Cupcake ipsum dolor sit amet ice cream.', | ||||||
|   original_file_name: 'sample.pdf', |   original_file_name: 'sample.pdf', | ||||||
|  |   archived_file_name: 'sample.pdf', | ||||||
|  |   mime_type: 'application/pdf', | ||||||
| } | } | ||||||
|  |  | ||||||
| describe('PreviewPopupComponent', () => { | describe('PreviewPopupComponent', () => { | ||||||
| @@ -23,6 +30,7 @@ describe('PreviewPopupComponent', () => { | |||||||
|   let fixture: ComponentFixture<PreviewPopupComponent> |   let fixture: ComponentFixture<PreviewPopupComponent> | ||||||
|   let settingsService: SettingsService |   let settingsService: SettingsService | ||||||
|   let documentService: DocumentService |   let documentService: DocumentService | ||||||
|  |   let http: HttpClient | ||||||
|  |  | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     TestBed.configureTestingModule({ |     TestBed.configureTestingModule({ | ||||||
| @@ -35,23 +43,22 @@ describe('PreviewPopupComponent', () => { | |||||||
|     }) |     }) | ||||||
|     settingsService = TestBed.inject(SettingsService) |     settingsService = TestBed.inject(SettingsService) | ||||||
|     documentService = TestBed.inject(DocumentService) |     documentService = TestBed.inject(DocumentService) | ||||||
|  |     http = TestBed.inject(HttpClient) | ||||||
|     jest |     jest | ||||||
|       .spyOn(documentService, 'getPreviewUrl') |       .spyOn(documentService, 'getPreviewUrl') | ||||||
|       .mockImplementation((id) => doc.original_file_name) |       .mockImplementation((id) => doc.original_file_name) | ||||||
|     fixture = TestBed.createComponent(PreviewPopupComponent) |     fixture = TestBed.createComponent(PreviewPopupComponent) | ||||||
|     component = fixture.componentInstance |     component = fixture.componentInstance | ||||||
|     component.document = doc |     component.document = { ...doc } | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   it('should guess if file is pdf by file name', () => { |   it('should correctly report if document is pdf', () => { | ||||||
|     expect(component.isPdf).toBeTruthy() |  | ||||||
|     component.document.archived_file_name = 'sample.pdf' |  | ||||||
|     expect(component.isPdf).toBeTruthy() |     expect(component.isPdf).toBeTruthy() | ||||||
|  |     component.document.mime_type = 'application/msword' | ||||||
|  |     expect(component.isPdf).toBeTruthy() // still has archive file | ||||||
|     component.document.archived_file_name = undefined |     component.document.archived_file_name = undefined | ||||||
|     component.document.original_file_name = 'sample.txt' |  | ||||||
|     expect(component.isPdf).toBeFalsy() |     expect(component.isPdf).toBeFalsy() | ||||||
|     component.document.original_file_name = 'sample.pdf' |  | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   it('should return settings for native PDF viewer', () => { |   it('should return settings for native PDF viewer', () => { | ||||||
| @@ -84,6 +91,8 @@ describe('PreviewPopupComponent', () => { | |||||||
|  |  | ||||||
|   it('should fall back to object for non-pdf', () => { |   it('should fall back to object for non-pdf', () => { | ||||||
|     component.document.original_file_name = 'sample.png' |     component.document.original_file_name = 'sample.png' | ||||||
|  |     component.document.mime_type = 'image/png' | ||||||
|  |     component.document.archived_file_name = undefined | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|     expect(fixture.debugElement.query(By.css('object'))).not.toBeNull() |     expect(fixture.debugElement.query(By.css('object'))).not.toBeNull() | ||||||
|   }) |   }) | ||||||
| @@ -95,4 +104,22 @@ describe('PreviewPopupComponent', () => { | |||||||
|       'Error loading preview' |       'Error loading preview' | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   it('should get text content from http if appropriate', () => { | ||||||
|  |     component.document = { | ||||||
|  |       ...doc, | ||||||
|  |       original_file_name: 'sample.txt', | ||||||
|  |       mime_type: 'text/plain', | ||||||
|  |     } | ||||||
|  |     const httpSpy = jest.spyOn(http, 'get') | ||||||
|  |     httpSpy.mockReturnValueOnce( | ||||||
|  |       throwError(() => new Error('Error getting preview')) | ||||||
|  |     ) | ||||||
|  |     component.init() | ||||||
|  |     expect(httpSpy).toHaveBeenCalled() | ||||||
|  |     expect(component.error).toBeTruthy() | ||||||
|  |     httpSpy.mockReturnValueOnce(of('Preview text')) | ||||||
|  |     component.init() | ||||||
|  |     expect(component.previewText).toEqual('Preview text') | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| import { Component, Input } from '@angular/core' | import { HttpClient } from '@angular/common/http' | ||||||
|  | import { Component, Input, OnDestroy } from '@angular/core' | ||||||
|  | import { first, Subject, takeUntil } from 'rxjs' | ||||||
| import { Document } from 'src/app/data/document' | import { Document } from 'src/app/data/document' | ||||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||||
| import { DocumentService } from 'src/app/services/rest/document.service' | import { DocumentService } from 'src/app/services/rest/document.service' | ||||||
| @@ -9,14 +11,26 @@ import { SettingsService } from 'src/app/services/settings.service' | |||||||
|   templateUrl: './preview-popup.component.html', |   templateUrl: './preview-popup.component.html', | ||||||
|   styleUrls: ['./preview-popup.component.scss'], |   styleUrls: ['./preview-popup.component.scss'], | ||||||
| }) | }) | ||||||
| export class PreviewPopupComponent { | export class PreviewPopupComponent implements OnDestroy { | ||||||
|  |   private _document: Document | ||||||
|   @Input() |   @Input() | ||||||
|   document: Document |   set document(document: Document) { | ||||||
|  |     this._document = document | ||||||
|  |     this.init() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get document(): Document { | ||||||
|  |     return this._document | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   unsubscribeNotifier: Subject<any> = new Subject() | ||||||
|  |  | ||||||
|   error = false |   error = false | ||||||
|  |  | ||||||
|   requiresPassword: boolean = false |   requiresPassword: boolean = false | ||||||
|  |  | ||||||
|  |   previewText: string | ||||||
|  |  | ||||||
|   get renderAsObject(): boolean { |   get renderAsObject(): boolean { | ||||||
|     return (this.isPdf && this.useNativePdfViewer) || !this.isPdf |     return (this.isPdf && this.useNativePdfViewer) || !this.isPdf | ||||||
|   } |   } | ||||||
| @@ -30,18 +44,38 @@ export class PreviewPopupComponent { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   get isPdf(): boolean { |   get isPdf(): boolean { | ||||||
|     // We dont have time to retrieve metadata, make a best guess by file name |  | ||||||
|     return ( |     return ( | ||||||
|       this.document?.original_file_name?.endsWith('.pdf') || |       this.document?.archived_file_name?.length > 0 || | ||||||
|       this.document?.archived_file_name?.endsWith('.pdf') |       this.document?.mime_type?.includes('pdf') | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     private settingsService: SettingsService, |     private settingsService: SettingsService, | ||||||
|     private documentService: DocumentService |     private documentService: DocumentService, | ||||||
|  |     private http: HttpClient | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|  |   ngOnDestroy(): void { | ||||||
|  |     this.unsubscribeNotifier.next(this) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   init() { | ||||||
|  |     if (this.document.mime_type?.includes('text')) { | ||||||
|  |       this.http | ||||||
|  |         .get(this.previewURL, { responseType: 'text' }) | ||||||
|  |         .pipe(first(), takeUntil(this.unsubscribeNotifier)) | ||||||
|  |         .subscribe({ | ||||||
|  |           next: (res) => { | ||||||
|  |             this.previewText = res.toString() | ||||||
|  |           }, | ||||||
|  |           error: (err) => { | ||||||
|  |             this.error = err | ||||||
|  |           }, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   onError(event: any) { |   onError(event: any) { | ||||||
|     if (event.name == 'PasswordException') { |     if (event.name == 'PasswordException') { | ||||||
|       this.requiresPassword = true |       this.requiresPassword = true | ||||||
|   | |||||||
| @@ -350,14 +350,16 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
|  |  | ||||||
| <ng-template #previewContent> | <ng-template #previewContent> | ||||||
|   @if (!metadata) { |   <div class="thumb-preview position-absolute pe-none" [class.fade]="previewLoaded"> | ||||||
|     <div class="w-100 h-100 d-flex align-items-center justify-content-center"> |     <img *ngIf="showThumbnailOverlay" [src]="thumbUrl | safeUrl" class="" width="100%" height="auto" alt="Document loading..." i18n-alt /> | ||||||
|  |     <div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center"> | ||||||
|       <div> |       <div> | ||||||
|         <div class="spinner-border spinner-border-sm me-2" role="status"></div> |         <div class="spinner-border spinner-border-sm me-2" role="status"></div> | ||||||
|         <ng-container i18n>Loading...</ng-container> |         <ng-container i18n>Loading...</ng-container> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   } @else { |   </div> | ||||||
|  |   @if (document) { | ||||||
|     @switch (archiveContentRenderType) { |     @switch (archiveContentRenderType) { | ||||||
|       @case (ContentRenderType.PDF) { |       @case (ContentRenderType.PDF) { | ||||||
|         @if (!useNativePdfViewer) { |         @if (!useNativePdfViewer) { | ||||||
|   | |||||||
| @@ -63,6 +63,27 @@ textarea.rtl { | |||||||
|   object-fit: contain; |   object-fit: contain; | ||||||
| } | } | ||||||
|  |  | ||||||
| .whitespace-preserve { | .thumb-preview { | ||||||
|   white-space: preserve; |   top: 0; | ||||||
|  |   left: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: calc(100vh - 160px); | ||||||
|  |  | ||||||
|  |   @media screen and (min-width: 768px) { | ||||||
|  |     left: calc(.5 * var(--bs-gutter-x)); | ||||||
|  |     width: calc(100% - var(--bs-gutter-x)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   overflow: hidden; | ||||||
|  |   background-color: gray; | ||||||
|  |   padding: 10px 8px; // border | ||||||
|  |   z-index: 1000; | ||||||
|  |  | ||||||
|  |   > div { | ||||||
|  |     mix-blend-mode: difference; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   > img { | ||||||
|  |     filter: blur(1px); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -774,6 +774,15 @@ describe('DocumentDetailComponent', () => { | |||||||
|     expect(component.previewNumPages).toEqual(1000) |     expect(component.previewNumPages).toEqual(1000) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   it('should include delay of 300ms after previewloaded before showing pdf', fakeAsync(() => { | ||||||
|  |     initNormally() | ||||||
|  |     expect(component.previewLoaded).toBeFalsy() | ||||||
|  |     component.pdfPreviewLoaded({ numPages: 1000 } as any) | ||||||
|  |     expect(component.previewNumPages).toEqual(1000) | ||||||
|  |     tick(300) | ||||||
|  |     expect(component.previewLoaded).toBeTruthy() | ||||||
|  |   })) | ||||||
|  |  | ||||||
|   it('should support zoom controls', () => { |   it('should support zoom controls', () => { | ||||||
|     initNormally() |     initNormally() | ||||||
|     component.onZoomSelect({ target: { value: '1' } } as any) // from select |     component.onZoomSelect({ target: { value: '1' } } as any) // from select | ||||||
| @@ -921,7 +930,7 @@ describe('DocumentDetailComponent', () => { | |||||||
|  |  | ||||||
|   it('should display built-in pdf viewer if not disabled', () => { |   it('should display built-in pdf viewer if not disabled', () => { | ||||||
|     initNormally() |     initNormally() | ||||||
|     component.metadata = { has_archive_version: true } |     component.document.archived_file_name = 'file.pdf' | ||||||
|     jest.spyOn(settingsService, 'get').mockReturnValue(false) |     jest.spyOn(settingsService, 'get').mockReturnValue(false) | ||||||
|     expect(component.useNativePdfViewer).toBeFalsy() |     expect(component.useNativePdfViewer).toBeFalsy() | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
| @@ -930,7 +939,7 @@ describe('DocumentDetailComponent', () => { | |||||||
|  |  | ||||||
|   it('should display native pdf viewer if enabled', () => { |   it('should display native pdf viewer if enabled', () => { | ||||||
|     initNormally() |     initNormally() | ||||||
|     component.metadata = { has_archive_version: true } |     component.document.archived_file_name = 'file.pdf' | ||||||
|     jest.spyOn(settingsService, 'get').mockReturnValue(true) |     jest.spyOn(settingsService, 'get').mockReturnValue(true) | ||||||
|     expect(component.useNativePdfViewer).toBeTruthy() |     expect(component.useNativePdfViewer).toBeTruthy() | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
| @@ -1072,8 +1081,8 @@ describe('DocumentDetailComponent', () => { | |||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   it('should change preview element by render type', () => { |   it('should change preview element by render type', () => { | ||||||
|     component.metadata = { has_archive_version: true } |  | ||||||
|     initNormally() |     initNormally() | ||||||
|  |     component.document.archived_file_name = 'file.pdf' | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|     expect(component.archiveContentRenderType).toEqual( |     expect(component.archiveContentRenderType).toEqual( | ||||||
|       component.ContentRenderType.PDF |       component.ContentRenderType.PDF | ||||||
| @@ -1082,10 +1091,8 @@ describe('DocumentDetailComponent', () => { | |||||||
|       fixture.debugElement.query(By.css('pdf-viewer-container')) |       fixture.debugElement.query(By.css('pdf-viewer-container')) | ||||||
|     ).not.toBeUndefined() |     ).not.toBeUndefined() | ||||||
|  |  | ||||||
|     component.metadata = { |     component.document.archived_file_name = undefined | ||||||
|       has_archive_version: false, |     component.document.mime_type = 'text/plain' | ||||||
|       original_mime_type: 'text/plain', |  | ||||||
|     } |  | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|     expect(component.archiveContentRenderType).toEqual( |     expect(component.archiveContentRenderType).toEqual( | ||||||
|       component.ContentRenderType.Text |       component.ContentRenderType.Text | ||||||
| @@ -1094,10 +1101,7 @@ describe('DocumentDetailComponent', () => { | |||||||
|       fixture.debugElement.query(By.css('div.preview-sticky')) |       fixture.debugElement.query(By.css('div.preview-sticky')) | ||||||
|     ).not.toBeUndefined() |     ).not.toBeUndefined() | ||||||
|  |  | ||||||
|     component.metadata = { |     component.document.mime_type = 'image/jpeg' | ||||||
|       has_archive_version: false, |  | ||||||
|       original_mime_type: 'image/jpg', |  | ||||||
|     } |  | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|     expect(component.archiveContentRenderType).toEqual( |     expect(component.archiveContentRenderType).toEqual( | ||||||
|       component.ContentRenderType.Image |       component.ContentRenderType.Image | ||||||
| @@ -1105,12 +1109,8 @@ describe('DocumentDetailComponent', () => { | |||||||
|     expect( |     expect( | ||||||
|       fixture.debugElement.query(By.css('.preview-sticky img')) |       fixture.debugElement.query(By.css('.preview-sticky img')) | ||||||
|     ).not.toBeUndefined() |     ).not.toBeUndefined() | ||||||
|  |     ;(component.document.mime_type = | ||||||
|     component.metadata = { |       'application/vnd.openxmlformats-officedocument.wordprocessingml.document'), | ||||||
|       has_archive_version: false, |  | ||||||
|       original_mime_type: |  | ||||||
|         'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |  | ||||||
|     } |  | ||||||
|       fixture.detectChanges() |       fixture.detectChanges() | ||||||
|     expect(component.archiveContentRenderType).toEqual( |     expect(component.archiveContentRenderType).toEqual( | ||||||
|       component.ContentRenderType.Other |       component.ContentRenderType.Other | ||||||
|   | |||||||
| @@ -131,9 +131,11 @@ export class DocumentDetailComponent | |||||||
|   title: string |   title: string | ||||||
|   titleSubject: Subject<string> = new Subject() |   titleSubject: Subject<string> = new Subject() | ||||||
|   previewUrl: string |   previewUrl: string | ||||||
|  |   thumbUrl: string | ||||||
|   previewText: string |   previewText: string | ||||||
|   downloadUrl: string |   downloadUrl: string | ||||||
|   downloadOriginalUrl: string |   downloadOriginalUrl: string | ||||||
|  |   previewLoaded: boolean = false | ||||||
|  |  | ||||||
|   correspondents: Correspondent[] |   correspondents: Correspondent[] | ||||||
|   documentTypes: DocumentType[] |   documentTypes: DocumentType[] | ||||||
| @@ -221,15 +223,17 @@ export class DocumentDetailComponent | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   get archiveContentRenderType(): ContentRenderType { |   get archiveContentRenderType(): ContentRenderType { | ||||||
|     return this.getRenderType( |     return this.document?.archived_file_name | ||||||
|       this.metadata?.has_archive_version |       ? this.getRenderType('application/pdf') | ||||||
|         ? 'application/pdf' |       : this.getRenderType(this.document?.mime_type) | ||||||
|         : this.metadata?.original_mime_type |  | ||||||
|     ) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get originalContentRenderType(): ContentRenderType { |   get originalContentRenderType(): ContentRenderType { | ||||||
|     return this.getRenderType(this.metadata?.original_mime_type) |     return this.getRenderType(this.document?.mime_type) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get showThumbnailOverlay(): boolean { | ||||||
|  |     return this.settings.get(SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private getRenderType(mimeType: string): ContentRenderType { |   private getRenderType(mimeType: string): ContentRenderType { | ||||||
| @@ -339,6 +343,7 @@ export class DocumentDetailComponent | |||||||
|               }` |               }` | ||||||
|             }, |             }, | ||||||
|           }) |           }) | ||||||
|  |           this.thumbUrl = this.documentsService.getThumbUrl(documentId) | ||||||
|           return this.documentsService.get(documentId) |           return this.documentsService.get(documentId) | ||||||
|         }) |         }) | ||||||
|       ) |       ) | ||||||
| @@ -537,6 +542,9 @@ export class DocumentDetailComponent | |||||||
|       .subscribe({ |       .subscribe({ | ||||||
|         next: (result) => { |         next: (result) => { | ||||||
|           this.metadata = result |           this.metadata = result | ||||||
|  |           if (this.archiveContentRenderType !== ContentRenderType.PDF) { | ||||||
|  |             this.previewLoaded = true | ||||||
|  |           } | ||||||
|         }, |         }, | ||||||
|         error: (error) => { |         error: (error) => { | ||||||
|           this.metadata = {} // allow display to fallback to <object> tag |           this.metadata = {} // allow display to fallback to <object> tag | ||||||
| @@ -903,11 +911,15 @@ export class DocumentDetailComponent | |||||||
|   pdfPreviewLoaded(pdf: PDFDocumentProxy) { |   pdfPreviewLoaded(pdf: PDFDocumentProxy) { | ||||||
|     this.previewNumPages = pdf.numPages |     this.previewNumPages = pdf.numPages | ||||||
|     if (this.password) this.requiresPassword = false |     if (this.password) this.requiresPassword = false | ||||||
|  |     setTimeout(() => { | ||||||
|  |       this.previewLoaded = true | ||||||
|  |     }, 300) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   onError(event) { |   onError(event) { | ||||||
|     if (event.name == 'PasswordException') { |     if (event.name == 'PasswordException') { | ||||||
|       this.requiresPassword = true |       this.requiresPassword = true | ||||||
|  |       this.previewLoaded = true | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -150,6 +150,8 @@ export interface Document extends ObjectWithPermissions { | |||||||
|  |  | ||||||
|   added?: Date |   added?: Date | ||||||
|  |  | ||||||
|  |   mime_type?: string | ||||||
|  |  | ||||||
|   deleted_at?: Date |   deleted_at?: Date | ||||||
|  |  | ||||||
|   original_file_name?: string |   original_file_name?: string | ||||||
|   | |||||||
| @@ -61,6 +61,8 @@ export const SETTINGS_KEYS = { | |||||||
|   DEFAULT_PERMS_EDIT_GROUPS: 'general-settings:permissions:default-edit-groups', |   DEFAULT_PERMS_EDIT_GROUPS: 'general-settings:permissions:default-edit-groups', | ||||||
|   DOCUMENT_EDITING_REMOVE_INBOX_TAGS: |   DOCUMENT_EDITING_REMOVE_INBOX_TAGS: | ||||||
|     'general-settings:document-editing:remove-inbox-tags', |     'general-settings:document-editing:remove-inbox-tags', | ||||||
|  |   DOCUMENT_EDITING_OVERLAY_THUMBNAIL: | ||||||
|  |     'general-settings:document-editing:overlay-thumbnail', | ||||||
|   SEARCH_DB_ONLY: 'general-settings:search:db-only', |   SEARCH_DB_ONLY: 'general-settings:search:db-only', | ||||||
|   SEARCH_FULL_TYPE: 'general-settings:search:more-link', |   SEARCH_FULL_TYPE: 'general-settings:search:more-link', | ||||||
|   EMPTY_TRASH_DELAY: 'trash_delay', |   EMPTY_TRASH_DELAY: 'trash_delay', | ||||||
| @@ -229,6 +231,11 @@ export const SETTINGS: UiSetting[] = [ | |||||||
|     type: 'boolean', |     type: 'boolean', | ||||||
|     default: false, |     default: false, | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     key: SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL, | ||||||
|  |     type: 'boolean', | ||||||
|  |     default: true, | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     key: SETTINGS_KEYS.SEARCH_DB_ONLY, |     key: SETTINGS_KEYS.SEARCH_DB_ONLY, | ||||||
|     type: 'boolean', |     type: 'boolean', | ||||||
|   | |||||||
| @@ -680,6 +680,10 @@ code { | |||||||
|   opacity: .5; |   opacity: .5; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .whitespace-preserve { | ||||||
|  |   white-space: preserve; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Animate items as they're being sorted. */ | /* Animate items as they're being sorted. */ | ||||||
| .cdk-drop-list-dragging .cdk-drag { | .cdk-drop-list-dragging .cdk-drag { | ||||||
|   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); |   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); | ||||||
|   | |||||||
| @@ -926,6 +926,7 @@ class DocumentSerializer( | |||||||
|             "custom_fields", |             "custom_fields", | ||||||
|             "remove_inbox_tags", |             "remove_inbox_tags", | ||||||
|             "page_count", |             "page_count", | ||||||
|  |             "mime_type", | ||||||
|         ) |         ) | ||||||
|         list_serializer_class = OwnedObjectListSerializer |         list_serializer_class = OwnedObjectListSerializer | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon