Feature: loading preview, better text popup preview ()

This commit is contained in:
shamoon 2024-11-12 16:20:52 -08:00 committed by GitHub
parent 74d0c9fda5
commit a283a65813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 192 additions and 95 deletions

@ -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