From ecb300845cb6fa37c46266434f15a058059bf8ef Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:23:26 -0800 Subject: [PATCH 01/11] Update frontend translation strings --- src-ui/messages.xlf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 3357ffc45..b4255aa63 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -4942,7 +4942,7 @@ Not assigned src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts - 343 + 351 Filter drop down element to filter for documents with no correspondent/type/tag assigned @@ -4950,7 +4950,7 @@ Open filter src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts - 455 + 463 From a186527f070ba378d41b1e78dec1d0ff97d0bdc7 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:24:48 -0800 Subject: [PATCH 02/11] Enhancement: use theme-color meta tag (#8359) --- src-ui/messages.xlf | 74 ++++++++++----------- src-ui/src/app/data/ui-settings.ts | 2 + src-ui/src/app/services/settings.service.ts | 14 +++- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index b4255aa63..012647605 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -9112,259 +9112,259 @@ English (US) src/app/services/settings.service.ts - 46 + 51 Afrikaans src/app/services/settings.service.ts - 52 + 57 Arabic src/app/services/settings.service.ts - 58 + 63 Belarusian src/app/services/settings.service.ts - 64 + 69 Bulgarian src/app/services/settings.service.ts - 70 + 75 Catalan src/app/services/settings.service.ts - 76 + 81 Czech src/app/services/settings.service.ts - 82 + 87 Danish src/app/services/settings.service.ts - 88 + 93 German src/app/services/settings.service.ts - 94 + 99 Greek src/app/services/settings.service.ts - 100 + 105 English (GB) src/app/services/settings.service.ts - 106 + 111 Spanish src/app/services/settings.service.ts - 112 + 117 Finnish src/app/services/settings.service.ts - 118 + 123 French src/app/services/settings.service.ts - 124 + 129 Hungarian src/app/services/settings.service.ts - 130 + 135 Italian src/app/services/settings.service.ts - 136 + 141 Japanese src/app/services/settings.service.ts - 142 + 147 Korean src/app/services/settings.service.ts - 148 + 153 Luxembourgish src/app/services/settings.service.ts - 154 + 159 Dutch src/app/services/settings.service.ts - 160 + 165 Norwegian src/app/services/settings.service.ts - 166 + 171 Polish src/app/services/settings.service.ts - 172 + 177 Portuguese (Brazil) src/app/services/settings.service.ts - 178 + 183 Portuguese src/app/services/settings.service.ts - 184 + 189 Romanian src/app/services/settings.service.ts - 190 + 195 Russian src/app/services/settings.service.ts - 196 + 201 Slovak src/app/services/settings.service.ts - 202 + 207 Slovenian src/app/services/settings.service.ts - 208 + 213 Serbian src/app/services/settings.service.ts - 214 + 219 Swedish src/app/services/settings.service.ts - 220 + 225 Turkish src/app/services/settings.service.ts - 226 + 231 Ukrainian src/app/services/settings.service.ts - 232 + 237 Chinese Simplified src/app/services/settings.service.ts - 238 + 243 ISO 8601 src/app/services/settings.service.ts - 246 + 251 Successfully completed one-time migratration of settings to the database! src/app/services/settings.service.ts - 574 + 584 Unable to migrate settings to the database, please try saving manually. src/app/services/settings.service.ts - 575 + 585 You can restart the tour from the settings page. src/app/services/settings.service.ts - 645 + 655 diff --git a/src-ui/src/app/data/ui-settings.ts b/src-ui/src/app/data/ui-settings.ts index d7a6c284e..dfdebb9e1 100644 --- a/src-ui/src/app/data/ui-settings.ts +++ b/src-ui/src/app/data/ui-settings.ts @@ -17,6 +17,8 @@ export enum GlobalSearchType { TITLE_CONTENT = 'title-content', } +export const PAPERLESS_GREEN_HEX = '#17541f' + export const SETTINGS_KEYS = { LANGUAGE: 'language', APP_LOGO: 'app_logo', diff --git a/src-ui/src/app/services/settings.service.ts b/src-ui/src/app/services/settings.service.ts index 5005a2865..670bf9943 100644 --- a/src-ui/src/app/services/settings.service.ts +++ b/src-ui/src/app/services/settings.service.ts @@ -17,7 +17,12 @@ import { hexToHsl, } from 'src/app/utils/color' import { environment } from 'src/environments/environment' -import { UiSettings, SETTINGS, SETTINGS_KEYS } from '../data/ui-settings' +import { + UiSettings, + SETTINGS, + SETTINGS_KEYS, + PAPERLESS_GREEN_HEX, +} from '../data/ui-settings' import { User } from '../data/user' import { PermissionAction, @@ -420,7 +425,7 @@ export class SettingsService { ) } - if (themeColor) { + if (themeColor?.length) { const hsl = hexToHsl(themeColor) const bgBrightnessEstimate = estimateBrightnessForColor(themeColor) @@ -445,6 +450,11 @@ export class SettingsService { document.documentElement.style.removeProperty('--pngx-primary') document.documentElement.style.removeProperty('--pngx-primary-lightness') } + + this.meta.updateTag({ + name: 'theme-color', + content: themeColor?.length ? themeColor : PAPERLESS_GREEN_HEX, + }) } getLanguageOptions(): LanguageOption[] { From c08ca6e3e571409a9285ea2191128e383293ea12 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:25:52 -0800 Subject: [PATCH 03/11] Fixhancement: dispatch change event from current field prior to save (#8369) --- .../app/components/document-detail/document-detail.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 9a9afadb8..6ea625651 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -721,6 +721,7 @@ export class DocumentDetailComponent save(close: boolean = false) { this.networkActive = true + ;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change')) this.documentsService .update(this.document) .pipe(first()) From 771b6606e0354a06fb075a59f5ac1329e5c5a913 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 28 Nov 2024 08:11:35 -0800 Subject: [PATCH 04/11] Fix: include subpath in logo url (#8374) --- src/documents/context_processors.py | 2 +- src/documents/tests/test_views.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/documents/context_processors.py b/src/documents/context_processors.py index a9200ac11..9a012bc3a 100644 --- a/src/documents/context_processors.py +++ b/src/documents/context_processors.py @@ -14,7 +14,7 @@ def settings(request): app_logo = ( django_settings.APP_LOGO if general_config.app_logo is None or len(general_config.app_logo) == 0 - else general_config.app_logo + else django_settings.BASE_URL + general_config.app_logo.lstrip("/") ) return { diff --git a/src/documents/tests/test_views.py b/src/documents/tests/test_views.py index ca1db5782..9f52a6aa4 100644 --- a/src/documents/tests/test_views.py +++ b/src/documents/tests/test_views.py @@ -6,12 +6,14 @@ from django.conf import settings from django.contrib.auth.models import Permission from django.contrib.auth.models import User from django.test import TestCase +from django.test import override_settings from django.utils import timezone from rest_framework import status from documents.models import Document from documents.models import ShareLink from documents.tests.utils import DirectoriesMixin +from paperless.models import ApplicationConfiguration class TestViews(DirectoriesMixin, TestCase): @@ -67,6 +69,26 @@ class TestViews(DirectoriesMixin, TestCase): f"frontend/{language_actual}/main.js", ) + @override_settings(BASE_URL="/paperless/") + def test_index_app_logo_with_base_url(self): + """ + GIVEN: + - Existing config with app_logo specified + WHEN: + - Index page is loaded + THEN: + - app_logo is prefixed with BASE_URL + """ + config = ApplicationConfiguration.objects.first() + config.app_logo = "/logo/example.jpg" + config.save() + self.client.force_login(self.user) + response = self.client.get("/") + self.assertEqual( + response.context["APP_LOGO"], + f"/paperless{config.app_logo}", + ) + def test_share_link_views(self): """ GIVEN: From 548a7f05d849e5a61fc06c748e52f98857ddc6d1 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:24:33 -0800 Subject: [PATCH 05/11] Enhancement: preview button for document list and trash, refactor (#8384) --- src-ui/messages.xlf | 66 +++++++++++-------- .../admin/trash/trash.component.html | 9 ++- .../preview-popup.component.html | 61 +++++++++-------- .../preview-popup.component.spec.ts | 41 ++++++++++-- .../preview-popup/preview-popup.component.ts | 50 +++++++++++++- .../document-card-large.component.html | 11 +--- .../document-card-large.component.spec.ts | 22 +------ .../document-card-large.component.ts | 27 +------- .../document-card-small.component.html | 11 +--- .../document-card-small.component.spec.ts | 22 +------ .../document-card-small.component.ts | 34 +--------- .../document-list.component.html | 7 +- src-ui/src/styles.scss | 29 ++++++-- src/documents/views.py | 2 +- 14 files changed, 210 insertions(+), 182 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 012647605..bd8b89095 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -1476,11 +1476,11 @@ src/app/components/admin/trash/trash.component.html - 67 + 72 src/app/components/admin/trash/trash.component.html - 76 + 81 src/app/components/admin/trash/trash.component.ts @@ -2199,25 +2199,25 @@ days src/app/components/admin/trash/trash.component.html - 58 + 63 Restore src/app/components/admin/trash/trash.component.html - 66 + 71 src/app/components/admin/trash/trash.component.html - 73 + 78 {VAR_PLURAL, plural, =1 {One document in trash} other { total documents in trash}} src/app/components/admin/trash/trash.component.html - 89 + 94 @@ -3019,11 +3019,11 @@ src/app/components/document-list/document-card-large/document-card-large.component.html - 68 + 63 src/app/components/document-list/document-card-small/document-card-small.component.html - 140 + 135 @@ -3312,7 +3312,7 @@ src/app/components/document-list/document-card-large/document-card-large.component.html - 62 + 60 @@ -5146,7 +5146,7 @@ src/app/components/document-list/document-card-small/document-card-small.component.ts - 86 + 79 @@ -5308,7 +5308,14 @@ Error loading preview src/app/components/common/preview-popup/preview-popup.component.html - 4 + 10 + + + + Open preview + + src/app/components/common/preview-popup/preview-popup.component.ts + 37 @@ -5922,11 +5929,11 @@ src/app/components/document-list/document-card-large/document-card-large.component.html - 79 + 74 src/app/components/document-list/document-list.component.html - 323 + 328 @@ -5937,11 +5944,11 @@ src/app/components/document-list/document-card-large/document-card-large.component.html - 85 + 80 src/app/components/document-list/document-list.component.html - 330 + 335 @@ -7130,21 +7137,21 @@ src/app/components/document-list/document-list.component.html - 299 + 304 View notes src/app/components/document-list/document-card-large/document-card-large.component.html - 74 + 69 Created: src/app/components/document-list/document-card-large/document-card-large.component.html - 98,99 + 93,94 src/app/components/document-list/document-card-small/document-card-small.component.html @@ -7159,7 +7166,7 @@ Added: src/app/components/document-list/document-card-large/document-card-large.component.html - 99,100 + 94,95 src/app/components/document-list/document-card-small/document-card-small.component.html @@ -7174,7 +7181,7 @@ Modified: src/app/components/document-list/document-card-large/document-card-large.component.html - 100,101 + 95,96 src/app/components/document-list/document-card-small/document-card-small.component.html @@ -7189,7 +7196,7 @@ {VAR_PLURAL, plural, =1 {1 page} other { pages}} src/app/components/document-list/document-card-large/document-card-large.component.html - 117 + 112 src/app/components/document-list/document-card-small/document-card-small.component.html @@ -7200,7 +7207,7 @@ Shared src/app/components/document-list/document-card-large/document-card-large.component.html - 127 + 122 src/app/components/document-list/document-card-small/document-card-small.component.html @@ -7219,7 +7226,7 @@ Score: src/app/components/document-list/document-card-large/document-card-large.component.html - 132 + 127 @@ -7473,14 +7480,21 @@ Edit document src/app/components/document-list/document-list.component.html - 295 + 296 + + + + Preview document + + src/app/components/document-list/document-list.component.html + 297 Yes src/app/components/document-list/document-list.component.html - 351 + 356 src/app/pipes/yes-no.pipe.ts @@ -7491,7 +7505,7 @@ No src/app/components/document-list/document-list.component.html - 351 + 356 src/app/pipes/yes-no.pipe.ts diff --git a/src-ui/src/app/components/admin/trash/trash.component.html b/src-ui/src/app/components/admin/trash/trash.component.html index 1c66bdd44..3dbf3db2b 100644 --- a/src-ui/src/app/components/admin/trash/trash.component.html +++ b/src-ui/src/app/components/admin/trash/trash.component.html @@ -47,14 +47,19 @@ } @for (document of documentsInTrash; track document.id) { - +
- {{ document.title }} + + {{ document.title }} + + + + {{ getDaysRemaining(document) }} days
diff --git a/src-ui/src/app/components/common/preview-popup/preview-popup.component.html b/src-ui/src/app/components/common/preview-popup/preview-popup.component.html index f9a8b9771..18b7cb94d 100644 --- a/src-ui/src/app/components/common/preview-popup/preview-popup.component.html +++ b/src-ui/src/app/components/common/preview-popup/preview-popup.component.html @@ -1,30 +1,37 @@ -
- @if (error) { -
-

Error loading preview

-
- } @else { - @if (renderAsObject) { - @if (previewText) { -
{{previewText}}
- } @else { - - } + + + + +
+ @if (error) { +
+

Error loading preview

+
} @else { - @if (requiresPassword) { -
- -
- } - @if (!requiresPassword) { - - + @if (renderAsObject) { + @if (previewText) { +
{{previewText}}
+ } @else { + + } + } @else { + @if (requiresPassword) { +
+ +
+ } + @if (!requiresPassword) { + + + } } } - } -
+
+ diff --git a/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts b/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts index 2b9f71cef..12021fc90 100644 --- a/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts +++ b/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts @@ -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() + })) }) diff --git a/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts b/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts index 6d2ede266..75f3cbb86 100644 --- a/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts +++ b/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts @@ -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 = 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) + } } diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html index 04f3a236a..34557be31 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html @@ -1,4 +1,4 @@ -
+
@@ -56,14 +56,9 @@  Open - +  View - - - - +  Download diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts index efd5076be..95b12d7ec 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts @@ -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 ... }) diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts index a3d57d950..99597ca5a 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts @@ -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() { diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html index 57bd6048b..60713ef02 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html @@ -1,5 +1,5 @@
-
+
@@ -129,14 +129,9 @@ - + - - - - + diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts index b86453a25..0c0c82103 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts @@ -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() - })) }) diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts index 5cd583fb0..7397159af 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts @@ -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 { diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 4eb9d179e..ebe3536e5 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -292,7 +292,12 @@ @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) { @if (activeDisplayFields.includes(DisplayField.TITLE)) { - {{d.title | documentTitle}} + } @if (activeDisplayFields.includes(DisplayField.TAGS)) { @for (t of d.tags$ | async; track t) { diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss index 331f6e6d8..fe1466d58 100644 --- a/src-ui/src/styles.scss +++ b/src-ui/src/styles.scss @@ -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; + } +} diff --git a/src/documents/views.py b/src/documents/views.py index 35fa8eafc..367559c6d 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -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", From 961452803322e733c19173507c09eb9aa5c7c7a5 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:36:40 -0800 Subject: [PATCH 06/11] Enhancement: filterable list count sorting and opacification (#8386) --- src-ui/messages.xlf | 74 +++++++++---------- .../filterable-dropdown.component.html | 10 ++- .../filterable-dropdown.component.spec.ts | 31 ++++++++ .../filterable-dropdown.component.ts | 29 +++++++- .../toggleable-dropdown-button.component.html | 9 ++- .../toggleable-dropdown-button.component.ts | 7 ++ 6 files changed, 116 insertions(+), 44 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index bd8b89095..33e9aacb8 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -2300,7 +2300,7 @@ src/app/components/document-detail/document-detail.component.ts - 846 + 847 @@ -2577,19 +2577,19 @@ src/app/components/document-detail/document-detail.component.ts - 870 + 871 src/app/components/document-detail/document-detail.component.ts - 1169 + 1170 src/app/components/document-detail/document-detail.component.ts - 1207 + 1208 src/app/components/document-detail/document-detail.component.ts - 1248 + 1249 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -3172,7 +3172,7 @@ src/app/components/document-detail/document-detail.component.ts - 823 + 824 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -4901,7 +4901,7 @@ Create src/app/components/common/filterable-dropdown/filterable-dropdown.component.html - 50 + 58 src/app/components/common/share-links-dropdown/share-links-dropdown.component.html @@ -4928,21 +4928,21 @@ Apply src/app/components/common/filterable-dropdown/filterable-dropdown.component.html - 56 + 64 Click again to exclude items. src/app/components/common/filterable-dropdown/filterable-dropdown.component.html - 63 + 71 Not assigned src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts - 351 + 370 Filter drop down element to filter for documents with no correspondent/type/tag assigned @@ -4950,7 +4950,7 @@ Open filter src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts - 463 + 486 @@ -6209,7 +6209,7 @@ src/app/components/document-detail/document-detail.component.ts - 1225 + 1226 src/app/guards/dirty-saved-view.guard.ts @@ -6573,36 +6573,36 @@ Document saved successfully. src/app/components/document-detail/document-detail.component.ts - 737 + 738 src/app/components/document-detail/document-detail.component.ts - 751 + 752 Error saving document src/app/components/document-detail/document-detail.component.ts - 755 + 756 src/app/components/document-detail/document-detail.component.ts - 796 + 797 Do you really want to move the document "" to the trash? src/app/components/document-detail/document-detail.component.ts - 824 + 825 Documents can be restored prior to permanent deletion. src/app/components/document-detail/document-detail.component.ts - 825 + 826 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -6613,7 +6613,7 @@ Move to trash src/app/components/document-detail/document-detail.component.ts - 827 + 828 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -6624,7 +6624,7 @@ Reprocess confirm src/app/components/document-detail/document-detail.component.ts - 866 + 867 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -6635,70 +6635,70 @@ This operation will permanently recreate the archive file for this document. src/app/components/document-detail/document-detail.component.ts - 867 + 868 The archive file will be re-generated with the current settings. src/app/components/document-detail/document-detail.component.ts - 868 + 869 Reprocess operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. src/app/components/document-detail/document-detail.component.ts - 878 + 879 Error executing operation src/app/components/document-detail/document-detail.component.ts - 889 + 890 Page Fit src/app/components/document-detail/document-detail.component.ts - 962 + 963 Split confirm src/app/components/document-detail/document-detail.component.ts - 1167 + 1168 This operation will split the selected document(s) into new documents. src/app/components/document-detail/document-detail.component.ts - 1168 + 1169 Split operation will begin in the background. src/app/components/document-detail/document-detail.component.ts - 1184 + 1185 Error executing split operation src/app/components/document-detail/document-detail.component.ts - 1193 + 1194 Rotate confirm src/app/components/document-detail/document-detail.component.ts - 1205 + 1206 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -6709,49 +6709,49 @@ This operation will permanently rotate the original version of the current document. src/app/components/document-detail/document-detail.component.ts - 1206 + 1207 Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes. src/app/components/document-detail/document-detail.component.ts - 1222 + 1223 Error executing rotate operation src/app/components/document-detail/document-detail.component.ts - 1234 + 1235 Delete pages confirm src/app/components/document-detail/document-detail.component.ts - 1246 + 1247 This operation will permanently delete the selected pages from the original document. src/app/components/document-detail/document-detail.component.ts - 1247 + 1248 Delete pages operation will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes. src/app/components/document-detail/document-detail.component.ts - 1262 + 1263 Error executing delete pages operation src/app/components/document-detail/document-detail.component.ts - 1271 + 1272 diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html index a3b49cf62..28ce03ad6 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html @@ -38,7 +38,15 @@ @for (item of selectionModel.items | filter: filterText:'name'; track item; let i = $index) { @if (allowSelectNone || item.id) { + [item]="item" + [hideCount]="hideCount(item)" + [opacifyCount]="!editing" + [state]="selectionModel.get(item.id)" + [count]="getUpdatedDocumentCount(item.id)" + (toggled)="selectionModel.toggle(item.id)" + (exclude)="excludeClicked(item.id)" + (click)="setButtonItemIndex(i - 1)" + [disabled]="disabled"> } } diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts index 0e2999742..78af75607 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts @@ -509,6 +509,37 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () => ]) }) + it('selection model should sort items by state and document counts, if set', () => { + component.items = items.concat([{ id: 4, name: 'Item D' }]) + component.selectionModel = selectionModel + component.documentCounts = [ + { id: 1, document_count: 0 }, // Tag1 + { id: 2, document_count: 1 }, // Tag2 + { id: 4, document_count: 2 }, + ] + component.selectionModel.apply() + expect(selectionModel.items).toEqual([ + nullItem, + { id: 4, name: 'Item D' }, + items[1], // Tag2 + items[0], // Tag1 + ]) + + selectionModel.toggle(items[1].id) + component.documentCounts = [ + { id: 1, document_count: 0 }, + { id: 2, document_count: 1 }, + { id: 4, document_count: 0 }, + ] + selectionModel.apply() + expect(selectionModel.items).toEqual([ + nullItem, + items[1], // Tag2 + { id: 4, name: 'Item D' }, + items[0], // Tag1 + ]) + }) + it('should set support create, keep open model and call createRef method', fakeAsync(() => { component.items = items component.icon = 'tag-fill' diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts index a23d413d7..2351dc0da 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts @@ -43,6 +43,11 @@ export class FilterableDropdownSelectionModel { private _intersection: Intersection = Intersection.Include temporaryIntersection: Intersection = this._intersection + private _documentCounts: SelectionDataItem[] = [] + public set documentCounts(counts: SelectionDataItem[]) { + this._documentCounts = counts + } + private _items: MatchingModel[] = [] get items(): MatchingModel[] { return this._items @@ -69,6 +74,16 @@ export class FilterableDropdownSelectionModel { this.getNonTemporary(b.id) == ToggleableItemState.NotSelected ) { return -1 + } else if ( + this._documentCounts.length && + this.getDocumentCount(a.id) > this.getDocumentCount(b.id) + ) { + return -1 + } else if ( + this._documentCounts.length && + this.getDocumentCount(a.id) < this.getDocumentCount(b.id) + ) { + return 1 } else { return a.name.localeCompare(b.name) } @@ -286,6 +301,10 @@ export class FilterableDropdownSelectionModel { ) } + getDocumentCount(id: number) { + return this._documentCounts.find((c) => c.id === id)?.document_count + } + init(map: Map) { this.temporarySelectionStates = map this.apply() @@ -431,7 +450,11 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit { } @Input() - documentCounts: SelectionDataItem[] + set documentCounts(counts: SelectionDataItem[]) { + if (counts) { + this.selectionModel.documentCounts = counts + } + } @Input() shortcutKey: string @@ -544,9 +567,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit { } getUpdatedDocumentCount(id: number) { - if (this.documentCounts) { - return this.documentCounts.find((c) => c.id === id)?.document_count - } + return this.selectionModel.getDocumentCount(id) } listKeyDown(event: KeyboardEvent) { diff --git a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html index 348393ced..1c7dad499 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html +++ b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html @@ -1,4 +1,9 @@ - diff --git a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.ts b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.ts index b3e18cf50..a5b3f4e31 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.ts +++ b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.ts @@ -29,6 +29,9 @@ export class ToggleableDropdownButtonComponent { @Input() hideCount: boolean = false + @Input() + opacifyCount: boolean = true + @Output() toggled = new EventEmitter() @@ -39,6 +42,10 @@ export class ToggleableDropdownButtonComponent { return 'is_inbox_tag' in this.item } + get currentCount(): number { + return this.count ?? this.item.document_count + } + toggleItem(event: MouseEvent): void { if (this.state == ToggleableItemState.Selected) { this.exclude.emit() From d1a4c1f2eb737abffa3bdeaaea293d714bad1a98 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:58:59 -0800 Subject: [PATCH 07/11] Enhancement: larger previews in action dialogs (#8387) --- src-ui/messages.xlf | 43 ++++++++----------- ...delete-pages-confirm-dialog.component.scss | 2 +- .../split-confirm-dialog.component.html | 10 ++--- .../split-confirm-dialog.component.scss | 2 +- .../document-detail.component.ts | 2 + .../bulk-editor/bulk-editor.component.ts | 2 +- 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 33e9aacb8..7e828b278 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -2581,15 +2581,15 @@ src/app/components/document-detail/document-detail.component.ts - 1170 + 1171 src/app/components/document-detail/document-detail.component.ts - 1208 + 1210 src/app/components/document-detail/document-detail.component.ts - 1249 + 1251 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -3293,7 +3293,7 @@ Delete original document after successful split src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html - 49 + 51 @@ -6209,7 +6209,7 @@ src/app/components/document-detail/document-detail.component.ts - 1226 + 1228 src/app/guards/dirty-saved-view.guard.ts @@ -6670,88 +6670,88 @@ Split confirm src/app/components/document-detail/document-detail.component.ts - 1168 + 1169 This operation will split the selected document(s) into new documents. src/app/components/document-detail/document-detail.component.ts - 1169 + 1170 Split operation will begin in the background. src/app/components/document-detail/document-detail.component.ts - 1185 + 1186 Error executing split operation src/app/components/document-detail/document-detail.component.ts - 1194 + 1195 Rotate confirm src/app/components/document-detail/document-detail.component.ts - 1206 + 1208 src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 787 + 788 This operation will permanently rotate the original version of the current document. src/app/components/document-detail/document-detail.component.ts - 1207 + 1209 Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes. src/app/components/document-detail/document-detail.component.ts - 1223 + 1225 Error executing rotate operation src/app/components/document-detail/document-detail.component.ts - 1235 + 1237 Delete pages confirm src/app/components/document-detail/document-detail.component.ts - 1247 + 1249 This operation will permanently delete the selected pages from the original document. src/app/components/document-detail/document-detail.component.ts - 1248 + 1250 Delete pages operation will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes. src/app/components/document-detail/document-detail.component.ts - 1263 + 1265 Error executing delete pages operation src/app/components/document-detail/document-detail.component.ts - 1272 + 1274 @@ -7096,13 +7096,6 @@ This operation will permanently rotate the original version of document(s). - - src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 788 - - - - This will alter the original copy. src/app/components/document-list/bulk-editor/bulk-editor.component.ts 789 diff --git a/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss b/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss index f74de973d..4ddd79bfa 100644 --- a/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss +++ b/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss @@ -1,6 +1,6 @@ .pdf-viewer-container { background-color: gray; - height: 350px; + height: 550px; pdf-viewer { width: 100%; diff --git a/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html b/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html index 7fb68218a..47e4c137c 100644 --- a/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html +++ b/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html @@ -6,7 +6,7 @@