mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Enhancement: preview button for document list and trash, refactor (#8384)
This commit is contained in:
parent
771b6606e0
commit
548a7f05d8
@ -1476,11 +1476,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
<context context-type="linenumber">67</context>
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
<context context-type="linenumber">76</context>
|
||||
<context context-type="linenumber">81</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
|
||||
@ -2199,25 +2199,25 @@
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ getDaysRemaining(document) }}"/> days</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
<context context-type="linenumber">58</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6770769801335635194" datatype="html">
|
||||
<source>Restore</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
<context context-type="linenumber">78</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2308646316372333720" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =1 {One document in trash} other {<x id="INTERPOLATION"/> total documents in trash}}</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
<context context-type="linenumber">94</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9021887951960049161" datatype="html">
|
||||
@ -3019,11 +3019,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">140</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="searchResults.noResults" datatype="html">
|
||||
@ -3312,7 +3312,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
<context context-type="linenumber">60</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9195188695728229921" datatype="html">
|
||||
@ -5146,7 +5146,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.ts</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
<context context-type="linenumber">79</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2504502765849142619" datatype="html">
|
||||
@ -5308,7 +5308,14 @@
|
||||
<source>Error loading preview</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/preview-popup/preview-popup.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
<context context-type="linenumber">10</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3601402187462260332" datatype="html">
|
||||
<source>Open preview</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/preview-popup/preview-popup.component.ts</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2984628903434675339" datatype="html">
|
||||
@ -5922,11 +5929,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">79</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">323</context>
|
||||
<context context-type="linenumber">328</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="157572966557284263" datatype="html">
|
||||
@ -5937,11 +5944,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">330</context>
|
||||
<context context-type="linenumber">335</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8911158217491828773" datatype="html">
|
||||
@ -7130,21 +7137,21 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">299</context>
|
||||
<context context-type="linenumber">304</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="106713086593101376" datatype="html">
|
||||
<source>View notes</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
<context context-type="linenumber">69</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3727324658595204357" datatype="html">
|
||||
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created_date | customDate }}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">98,99</context>
|
||||
<context context-type="linenumber">93,94</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
@ -7159,7 +7166,7 @@
|
||||
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">99,100</context>
|
||||
<context context-type="linenumber">94,95</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
@ -7174,7 +7181,7 @@
|
||||
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">100,101</context>
|
||||
<context context-type="linenumber">95,96</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
@ -7189,7 +7196,7 @@
|
||||
<source>{VAR_PLURAL, plural, =1 {1 page} other {<x id="INTERPOLATION"/> pages}}</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
@ -7200,7 +7207,7 @@
|
||||
<source>Shared</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
<context context-type="linenumber">122</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
@ -7219,7 +7226,7 @@
|
||||
<source>Score:</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3661756380991326939" datatype="html">
|
||||
@ -7473,14 +7480,21 @@
|
||||
<source>Edit document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">295</context>
|
||||
<context context-type="linenumber">296</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3420321797707163677" datatype="html">
|
||||
<source>Preview document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">297</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2807800733729323332" datatype="html">
|
||||
<source>Yes</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">351</context>
|
||||
<context context-type="linenumber">356</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
|
||||
@ -7491,7 +7505,7 @@
|
||||
<source>No</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">351</context>
|
||||
<context context-type="linenumber">356</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
|
||||
|
@ -47,14 +47,19 @@
|
||||
</tr>
|
||||
}
|
||||
@for (document of documentsInTrash; track document.id) {
|
||||
<tr (click)="toggleSelected(document); $event.stopPropagation();">
|
||||
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()">
|
||||
<td>
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="{{document.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row">{{ document.title }}</td>
|
||||
<td scope="row">
|
||||
{{ document.title }}
|
||||
<pngx-preview-popup [document]="document" linkClasses="btn btn-sm btn-link" #popupPreview>
|
||||
<i-bs name="eye"></i-bs>
|
||||
</pngx-preview-popup>
|
||||
</td>
|
||||
<td scope="row" i18n>{{ getDaysRemaining(document) }} days</td>
|
||||
<td scope="row">
|
||||
<div class="btn-group d-block d-sm-none">
|
||||
|
@ -1,30 +1,37 @@
|
||||
<div class="preview-popup-container">
|
||||
@if (error) {
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<p class="fst-italic position-absolute top-50 start-50 translate-middle" i18n>Error loading preview</p>
|
||||
</div>
|
||||
} @else {
|
||||
@if (renderAsObject) {
|
||||
@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>
|
||||
}
|
||||
<a [href]="link ?? previewUrl" class="{{linkClasses}}" [target]="linkTarget" [title]="linkTitle"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle" container="body"
|
||||
autoClose="true" [popoverClass]="popoverClass" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<ng-content></ng-content>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<div class="preview-popup-container">
|
||||
@if (error) {
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<p class="fst-italic position-absolute top-50 start-50 translate-middle" i18n>Error loading preview</p>
|
||||
</div>
|
||||
} @else {
|
||||
@if (requiresPassword) {
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<i-bs width="2em" height="2em" class="position-absolute top-50 start-50 translate-middle" name="file-earmark-lock"></i-bs>
|
||||
</div>
|
||||
}
|
||||
@if (!requiresPassword) {
|
||||
<pdf-viewer
|
||||
[src]="previewURL"
|
||||
[original-size]="false"
|
||||
[show-borders]="false"
|
||||
[show-all]="true"
|
||||
(error)="onError($event)">
|
||||
</pdf-viewer>
|
||||
@if (renderAsObject) {
|
||||
@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 {
|
||||
@if (requiresPassword) {
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<i-bs width="2em" height="2em" class="position-absolute top-50 start-50 translate-middle" name="file-earmark-lock"></i-bs>
|
||||
</div>
|
||||
}
|
||||
@if (!requiresPassword) {
|
||||
<pdf-viewer
|
||||
[src]="previewURL"
|
||||
[original-size]="false"
|
||||
[show-borders]="false"
|
||||
[show-all]="true"
|
||||
(error)="onError($event)">
|
||||
</pdf-viewer>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
TestBed,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
|
||||
import { PreviewPopupComponent } from './preview-popup.component'
|
||||
import { By } from '@angular/platform-browser'
|
||||
@ -15,6 +20,8 @@ import {
|
||||
withInterceptorsFromDi,
|
||||
} from '@angular/common/http'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||
|
||||
const doc = {
|
||||
id: 10,
|
||||
@ -34,8 +41,12 @@ describe('PreviewPopupComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PreviewPopupComponent, SafeUrlPipe],
|
||||
imports: [NgxBootstrapIconsModule.pick(allIcons), PdfViewerModule],
|
||||
declarations: [PreviewPopupComponent, SafeUrlPipe, DocumentTitlePipe],
|
||||
imports: [
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
PdfViewerModule,
|
||||
NgbPopoverModule,
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
@ -70,12 +81,14 @@ describe('PreviewPopupComponent', () => {
|
||||
|
||||
it('should render object if native PDF viewer enabled', () => {
|
||||
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, true)
|
||||
component.popover.open()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should render pngx viewer if native PDF viewer disabled', () => {
|
||||
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
|
||||
component.popover.open()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('object'))).toBeNull()
|
||||
expect(fixture.debugElement.query(By.css('pdf-viewer'))).not.toBeNull()
|
||||
@ -83,6 +96,7 @@ describe('PreviewPopupComponent', () => {
|
||||
|
||||
it('should show lock icon on password error', () => {
|
||||
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
|
||||
component.popover.open()
|
||||
component.onError({ name: 'PasswordException' })
|
||||
fixture.detectChanges()
|
||||
expect(component.requiresPassword).toBeTruthy()
|
||||
@ -93,16 +107,18 @@ describe('PreviewPopupComponent', () => {
|
||||
component.document.original_file_name = 'sample.png'
|
||||
component.document.mime_type = 'image/png'
|
||||
component.document.archived_file_name = undefined
|
||||
component.popover.open()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should show message on error', () => {
|
||||
component.popover.open()
|
||||
component.onError({})
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
'Error loading preview'
|
||||
)
|
||||
expect(
|
||||
fixture.debugElement.query(By.css('.popover')).nativeElement.textContent
|
||||
).toContain('Error loading preview')
|
||||
})
|
||||
|
||||
it('should get text content from http if appropriate', () => {
|
||||
@ -122,4 +138,17 @@ describe('PreviewPopupComponent', () => {
|
||||
component.init()
|
||||
expect(component.previewText).toEqual('Preview text')
|
||||
})
|
||||
|
||||
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
||||
component.mouseEnterPreview()
|
||||
expect(component.popover.isOpen()).toBeTruthy()
|
||||
tick(600)
|
||||
component.close()
|
||||
|
||||
component.mouseEnterPreview()
|
||||
tick(100)
|
||||
component.mouseLeavePreview()
|
||||
tick(600)
|
||||
expect(component.popover.isOpen()).toBeFalsy()
|
||||
}))
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Component, Input, OnDestroy } from '@angular/core'
|
||||
import { Component, Input, OnDestroy, ViewChild } from '@angular/core'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { first, Subject, takeUntil } from 'rxjs'
|
||||
import { Document } from 'src/app/data/document'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
@ -23,6 +24,18 @@ export class PreviewPopupComponent implements OnDestroy {
|
||||
return this._document
|
||||
}
|
||||
|
||||
@Input()
|
||||
link: string
|
||||
|
||||
@Input()
|
||||
linkClasses: string = 'btn btn-sm btn-outline-secondary'
|
||||
|
||||
@Input()
|
||||
linkTarget: string = '_blank'
|
||||
|
||||
@Input()
|
||||
linkTitle: string = $localize`Open preview`
|
||||
|
||||
unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
error = false
|
||||
@ -31,6 +44,12 @@ export class PreviewPopupComponent implements OnDestroy {
|
||||
|
||||
previewText: string
|
||||
|
||||
@ViewChild('popover') popover: NgbPopover
|
||||
|
||||
mouseOnPreview: boolean
|
||||
|
||||
popoverClass: string = 'shadow popover-preview'
|
||||
|
||||
get renderAsObject(): boolean {
|
||||
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
|
||||
}
|
||||
@ -83,4 +102,33 @@ export class PreviewPopupComponent implements OnDestroy {
|
||||
this.error = true
|
||||
}
|
||||
}
|
||||
|
||||
get previewUrl() {
|
||||
return this.documentService.getPreviewUrl(this.document.id)
|
||||
}
|
||||
|
||||
mouseEnterPreview() {
|
||||
this.mouseOnPreview = true
|
||||
if (!this.popover.isOpen()) {
|
||||
// we're going to open but hide to pre-load content during hover delay
|
||||
this.popover.open()
|
||||
this.popoverClass = 'shadow popover-preview pe-none opacity-0'
|
||||
setTimeout(() => {
|
||||
if (this.mouseOnPreview) {
|
||||
// show popover
|
||||
this.popoverClass = this.popoverClass.replace('pe-none opacity-0', '')
|
||||
} else {
|
||||
this.popover.close()
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeavePreview() {
|
||||
this.mouseOnPreview = false
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.popover.close(false)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="card mb-3 shadow-sm bg-light" [class.card-selected]="selected" [class.document-card]="selectable" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="card mb-3 shadow-sm bg-light" [class.card-selected]="selected" [class.document-card]="selectable" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 doc-img-container rounded-start" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit()">
|
||||
<img [src]="getThumbUrl()" class="card-img doc-img border-end rounded-start" [class.inverted]="getIsThumbInverted()">
|
||||
@ -56,14 +56,9 @@
|
||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<i-bs name="file-earmark-richtext"></i-bs> <span class="d-none d-md-inline" i18n>Open</span>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" target="_blank" [href]="previewUrl"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<pngx-preview-popup [document]="document" #popupPreview>
|
||||
<i-bs name="eye"></i-bs> <span class="d-none d-md-inline" i18n>View</span>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<pngx-preview-popup [document]="document"></pngx-preview-popup>
|
||||
</ng-template>
|
||||
</pngx-preview-popup>
|
||||
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
|
||||
<i-bs name="download"></i-bs> <span class="d-none d-md-inline" i18n>Download</span>
|
||||
</a>
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import {
|
||||
@ -84,21 +79,6 @@ describe('DocumentCardLargeComponent', () => {
|
||||
expect(fixture.nativeElement.textContent).toContain('8 pages')
|
||||
})
|
||||
|
||||
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
||||
component.mouseEnterPreview()
|
||||
expect(component.popover.isOpen()).toBeTruthy()
|
||||
expect(component.popoverHidden).toBeTruthy()
|
||||
tick(600)
|
||||
expect(component.popoverHidden).toBeFalsy()
|
||||
component.mouseLeaveCard()
|
||||
|
||||
component.mouseEnterPreview()
|
||||
tick(100)
|
||||
component.mouseLeavePreview()
|
||||
tick(600)
|
||||
expect(component.popover.isOpen()).toBeFalsy()
|
||||
}))
|
||||
|
||||
it('should trim content', () => {
|
||||
expect(component.contentTrimmed).toHaveLength(503) // includes ...
|
||||
})
|
||||
|
@ -12,9 +12,9 @@ import {
|
||||
} from 'src/app/data/document'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-document-card-large',
|
||||
@ -65,7 +65,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
||||
@Output()
|
||||
clickMoreLike = new EventEmitter()
|
||||
|
||||
@ViewChild('popover') popover: NgbPopover
|
||||
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
||||
|
||||
mouseOnPreview = false
|
||||
popoverHidden = true
|
||||
@ -112,29 +112,8 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
||||
return this.documentService.getPreviewUrl(this.document.id)
|
||||
}
|
||||
|
||||
mouseEnterPreview() {
|
||||
this.mouseOnPreview = true
|
||||
if (!this.popover.isOpen()) {
|
||||
// we're going to open but hide to pre-load content during hover delay
|
||||
this.popover.open()
|
||||
this.popoverHidden = true
|
||||
setTimeout(() => {
|
||||
if (this.mouseOnPreview) {
|
||||
// show popover
|
||||
this.popoverHidden = false
|
||||
} else {
|
||||
this.popover.close()
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeavePreview() {
|
||||
this.mouseOnPreview = false
|
||||
}
|
||||
|
||||
mouseLeaveCard() {
|
||||
this.popover.close()
|
||||
this.popupPreview.close()
|
||||
}
|
||||
|
||||
get contentTrimmed() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="col p-2 h-100">
|
||||
<div class="card h-100 shadow-sm document-card" [class.card-selected]="selected" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="card h-100 shadow-sm document-card" [class.card-selected]="selected" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="border-bottom doc-img-container rounded-top" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit(this)">
|
||||
<img class="card-img doc-img" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()">
|
||||
|
||||
@ -129,14 +129,9 @@
|
||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Open" i18n-title *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
|
||||
<i-bs name="file-earmark-richtext"></i-bs>
|
||||
</a>
|
||||
<a [href]="previewUrl" target="_blank" class="btn btn-sm btn-outline-secondary"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<pngx-preview-popup [document]="document" #popupPreview>
|
||||
<i-bs name="eye"></i-bs>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<pngx-preview-popup [document]="document"></pngx-preview-popup>
|
||||
</ng-template>
|
||||
</pngx-preview-popup>
|
||||
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
<i-bs name="download"></i-bs>
|
||||
</a>
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import {
|
||||
NgbPopoverModule,
|
||||
@ -116,19 +111,4 @@ describe('DocumentCardSmallComponent', () => {
|
||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||
).toHaveLength(6)
|
||||
})
|
||||
|
||||
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
||||
component.mouseEnterPreview()
|
||||
expect(component.popover.isOpen()).toBeTruthy()
|
||||
expect(component.popoverHidden).toBeTruthy()
|
||||
tick(600)
|
||||
expect(component.popoverHidden).toBeFalsy()
|
||||
component.mouseLeaveCard()
|
||||
|
||||
component.mouseEnterPreview()
|
||||
tick(100)
|
||||
component.mouseLeavePreview()
|
||||
tick(600)
|
||||
expect(component.popover.isOpen()).toBeFalsy()
|
||||
}))
|
||||
})
|
||||
|
@ -13,9 +13,9 @@ import {
|
||||
} from 'src/app/data/document'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-document-card-small',
|
||||
@ -61,10 +61,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
|
||||
moreTags: number = null
|
||||
|
||||
@ViewChild('popover') popover: NgbPopover
|
||||
|
||||
mouseOnPreview = false
|
||||
popoverHidden = true
|
||||
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
||||
|
||||
getIsThumbInverted() {
|
||||
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
|
||||
@ -78,10 +75,6 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
return this.documentService.getDownloadUrl(this.document.id)
|
||||
}
|
||||
|
||||
get previewUrl() {
|
||||
return this.documentService.getPreviewUrl(this.document.id)
|
||||
}
|
||||
|
||||
get privateName() {
|
||||
return $localize`Private`
|
||||
}
|
||||
@ -100,29 +93,8 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
)
|
||||
}
|
||||
|
||||
mouseEnterPreview() {
|
||||
this.mouseOnPreview = true
|
||||
if (!this.popover.isOpen()) {
|
||||
// we're going to open but hide to pre-load content during hover delay
|
||||
this.popover.open()
|
||||
this.popoverHidden = true
|
||||
setTimeout(() => {
|
||||
if (this.mouseOnPreview) {
|
||||
// show popover
|
||||
this.popoverHidden = false
|
||||
} else {
|
||||
this.popover.close()
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeavePreview() {
|
||||
this.mouseOnPreview = false
|
||||
}
|
||||
|
||||
mouseLeaveCard() {
|
||||
this.popover.close()
|
||||
this.popupPreview.close()
|
||||
}
|
||||
|
||||
get notesEnabled(): boolean {
|
||||
|
@ -292,7 +292,12 @@
|
||||
@if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
|
||||
<td width="30%">
|
||||
@if (activeDisplayFields.includes(DisplayField.TITLE)) {
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
<div class="d-inline-block" (mouseleave)="popupPreview.close()">
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
<pngx-preview-popup [document]="d" linkClasses="btn btn-sm btn-link text-secondary" linkTitle="Preview document" (click)="$event.stopPropagation()" i18n-linkTitle #popupPreview>
|
||||
<i-bs name="eye"></i-bs>
|
||||
</pngx-preview-popup>
|
||||
</div>
|
||||
}
|
||||
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
|
||||
@for (t of d.tags$ | async; track t) {
|
||||
|
@ -564,11 +564,6 @@ table.table {
|
||||
}
|
||||
}
|
||||
|
||||
.popover-hidden .popover {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Tour
|
||||
.tour-active .popover {
|
||||
min-width: 360px;
|
||||
@ -728,3 +723,27 @@ i-bs svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
// fixes for buttons in preview popup
|
||||
.btn-group pngx-preview-popup:not(:last-child) {
|
||||
// Prevent double borders when buttons are next to each other
|
||||
> .btn {
|
||||
margin-left: calc(#{$btn-border-width} * -1);
|
||||
}
|
||||
> .btn {
|
||||
@include border-end-radius(0);
|
||||
}
|
||||
}
|
||||
.btn-group pngx-preview-popup:not(:first-child) {
|
||||
> .btn {
|
||||
@include border-start-radius(0);
|
||||
}
|
||||
}
|
||||
.btn-group pngx-preview-popup {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
|
||||
> .btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -426,7 +426,7 @@ class DocumentViewSet(
|
||||
)
|
||||
|
||||
def file_response(self, pk, request, disposition):
|
||||
doc = Document.objects.select_related("owner").get(id=pk)
|
||||
doc = Document.global_objects.select_related("owner").get(id=pk)
|
||||
if request.user is not None and not has_perms_owner_aware(
|
||||
request.user,
|
||||
"view_document",
|
||||
|
Loading…
x
Reference in New Issue
Block a user