mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Feature: loading preview, better text popup preview (#8011)
This commit is contained in:
parent
74d0c9fda5
commit
a283a65813
src-ui/src
app
components
admin/settings
common/preview-popup
preview-popup.component.htmlpreview-popup.component.scsspreview-popup.component.spec.tspreview-popup.component.ts
document-detail
data
src/documents
@ -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,13 +1109,9 @@ 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,
|
fixture.detectChanges()
|
||||||
original_mime_type:
|
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
||||||
}
|
|
||||||
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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user