mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			2337fc155c
			...
			feature-pr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4070cd0e1b | 
@@ -1,10 +1,14 @@
 | 
			
		||||
<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>
 | 
			
		||||
@if (!previewOnly) {
 | 
			
		||||
  <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>
 | 
			
		||||
} @else {
 | 
			
		||||
  <ng-container [ngTemplateOutlet]="previewContent" [ngTemplateOutletContext]="{ $implicit: document }"></ng-container>
 | 
			
		||||
}
 | 
			
		||||
<ng-template #previewContent>
 | 
			
		||||
  <div class="preview-popup-container" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview(); close()">
 | 
			
		||||
  <div class="preview-popup-container" [class.full-size]="previewOnly" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview(); close()">
 | 
			
		||||
    @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>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,16 @@
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.preview-popup-container.full-size {
 | 
			
		||||
  width: 100% !important;
 | 
			
		||||
  height: 100% !important;
 | 
			
		||||
 | 
			
		||||
  > * {
 | 
			
		||||
    width: 100% !important;
 | 
			
		||||
    height: 100% !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep .popover.popover-preview {
 | 
			
		||||
    max-width: 32rem;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import { NgTemplateOutlet } from '@angular/common'
 | 
			
		||||
import { HttpClient } from '@angular/common/http'
 | 
			
		||||
import { Component, Input, OnDestroy, ViewChild } from '@angular/core'
 | 
			
		||||
import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
@@ -17,6 +18,7 @@ import { SettingsService } from 'src/app/services/settings.service'
 | 
			
		||||
  styleUrls: ['./preview-popup.component.scss'],
 | 
			
		||||
  imports: [
 | 
			
		||||
    NgbPopoverModule,
 | 
			
		||||
    NgTemplateOutlet,
 | 
			
		||||
    DocumentTitlePipe,
 | 
			
		||||
    PdfViewerModule,
 | 
			
		||||
    SafeUrlPipe,
 | 
			
		||||
@@ -47,6 +49,9 @@ export class PreviewPopupComponent implements OnDestroy {
 | 
			
		||||
  @Input()
 | 
			
		||||
  linkTitle: string = $localize`Open preview`
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  previewOnly: boolean = false
 | 
			
		||||
 | 
			
		||||
  unsubscribeNotifier: Subject<any> = new Subject()
 | 
			
		||||
 | 
			
		||||
  error = false
 | 
			
		||||
@@ -91,6 +96,8 @@ export class PreviewPopupComponent implements OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {
 | 
			
		||||
    this.error = false
 | 
			
		||||
    this.requiresPassword = false
 | 
			
		||||
    if (this.document.mime_type?.includes('text')) {
 | 
			
		||||
      this.http
 | 
			
		||||
        .get(this.previewURL, { responseType: 'text' })
 | 
			
		||||
@@ -119,6 +126,7 @@ export class PreviewPopupComponent implements OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mouseEnterPreview() {
 | 
			
		||||
    if (this.previewOnly) return
 | 
			
		||||
    this.mouseOnPreview = true
 | 
			
		||||
    if (!this.popover.isOpen()) {
 | 
			
		||||
      // we're going to open but hide to pre-load content during hover delay
 | 
			
		||||
@@ -136,10 +144,12 @@ export class PreviewPopupComponent implements OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mouseLeavePreview() {
 | 
			
		||||
    if (this.previewOnly) return
 | 
			
		||||
    this.mouseOnPreview = false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public close(immediate: boolean = false) {
 | 
			
		||||
    if (this.previewOnly) return
 | 
			
		||||
    setTimeout(
 | 
			
		||||
      () => {
 | 
			
		||||
        if (!this.mouseOnPreview) this.popover.close()
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="btn-group flex-fill" role="group">
 | 
			
		||||
    <input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="table" id="displayModeDetails" name="displayModeDetails">
 | 
			
		||||
    <label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
 | 
			
		||||
@@ -42,6 +43,13 @@
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="btn-group flex-fill" role="group">
 | 
			
		||||
    <input type="checkbox" class="btn-check" [(ngModel)]="list.showPreviewPane" value="table" id="previewPane" name="previewPane">
 | 
			
		||||
    <label for="previewPane" class="btn btn-outline-primary btn-sm">
 | 
			
		||||
      <i-bs name="window-split"></i-bs>
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div ngbDropdown class="btn-group flex-fill">
 | 
			
		||||
    <button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle>
 | 
			
		||||
      <i-bs name="arrow-down-up"></i-bs>
 | 
			
		||||
@@ -105,298 +113,335 @@
 | 
			
		||||
  <pngx-bulk-editor [hidden]="!isBulkEditing" [disabled]="!isBulkEditing"></pngx-bulk-editor>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<ng-template #pagination>
 | 
			
		||||
  <div class="d-flex flex-wrap gap-3 justify-content-between align-items-center mb-3">
 | 
			
		||||
    <div class="d-flex align-items-center">
 | 
			
		||||
      @if (list.isReloading) {
 | 
			
		||||
        <div class="spinner-border spinner-border-sm me-2" role="status"></div>
 | 
			
		||||
        <ng-container i18n>Loading...</ng-container>
 | 
			
		||||
      }
 | 
			
		||||
      @if (list.selected.size > 0) {
 | 
			
		||||
        <span i18n>{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
 | 
			
		||||
      }
 | 
			
		||||
      @if (!list.isReloading) {
 | 
			
		||||
        @if (list.selected.size === 0) {
 | 
			
		||||
          <span i18n>{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>
 | 
			
		||||
          } @if (isFiltered) {
 | 
			
		||||
             <span i18n>(filtered)</span>
 | 
			
		||||
<div class="row">
 | 
			
		||||
  <div [class.col-lg-6]="list.showPreviewPane" [class.col]="!list.showPreviewPane">
 | 
			
		||||
  <ng-template #pagination>
 | 
			
		||||
    <div class="d-flex flex-wrap gap-3 justify-content-between align-items-center mb-3">
 | 
			
		||||
      <div class="d-flex align-items-center">
 | 
			
		||||
        @if (list.isReloading) {
 | 
			
		||||
          <div class="spinner-border spinner-border-sm me-2" role="status"></div>
 | 
			
		||||
          <ng-container i18n>Loading...</ng-container>
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      @if (!list.isReloading && isFiltered) {
 | 
			
		||||
        <button class="btn btn-link py-0" (click)="resetFilters()">
 | 
			
		||||
          <i-bs width="1em" height="1em" name="x"></i-bs><small i18n>Reset filters</small>
 | 
			
		||||
          </button>
 | 
			
		||||
        @if (list.selected.size > 0) {
 | 
			
		||||
          <span i18n>{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
 | 
			
		||||
        }
 | 
			
		||||
        @if (!list.isReloading) {
 | 
			
		||||
          @if (list.selected.size === 0) {
 | 
			
		||||
            <span i18n>{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>
 | 
			
		||||
            } @if (isFiltered) {
 | 
			
		||||
               <span i18n>(filtered)</span>
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @if (!list.isReloading && isFiltered) {
 | 
			
		||||
          <button class="btn btn-link py-0" (click)="resetFilters()">
 | 
			
		||||
            <i-bs width="1em" height="1em" name="x"></i-bs><small i18n>Reset filters</small>
 | 
			
		||||
            </button>
 | 
			
		||||
          }
 | 
			
		||||
        </div>
 | 
			
		||||
        @if (list.collectionSize) {
 | 
			
		||||
          <ngb-pagination [pageSize]="list.pageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
 | 
			
		||||
          [rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
 | 
			
		||||
        }
 | 
			
		||||
      </div>
 | 
			
		||||
      @if (list.collectionSize) {
 | 
			
		||||
        <ngb-pagination [pageSize]="list.pageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
 | 
			
		||||
        [rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
 | 
			
		||||
      }
 | 
			
		||||
    </ng-template>
 | 
			
		||||
 | 
			
		||||
    <div tourAnchor="tour.documents">
 | 
			
		||||
      <ng-container *ngTemplateOutlet="pagination"></ng-container>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <div tourAnchor="tour.documents">
 | 
			
		||||
    <ng-container *ngTemplateOutlet="pagination"></ng-container>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  @if (list.error ) {
 | 
			
		||||
    <div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
 | 
			
		||||
  } @else {
 | 
			
		||||
    @if (list.displayMode === DisplayMode.LARGE_CARDS) {
 | 
			
		||||
      <div>
 | 
			
		||||
        @for (d of list.documents; track d.id) {
 | 
			
		||||
          <pngx-document-card-large
 | 
			
		||||
            [selected]="list.isSelected(d)"
 | 
			
		||||
            (toggleSelected)="toggleSelected(d, $event)"
 | 
			
		||||
            (dblClickDocument)="openDocumentDetail(d)"
 | 
			
		||||
            [document]="d"
 | 
			
		||||
            [displayFields]="activeDisplayFields"
 | 
			
		||||
            (clickTag)="clickTag($event)"
 | 
			
		||||
            (clickCorrespondent)="clickCorrespondent($event)"
 | 
			
		||||
            (clickDocumentType)="clickDocumentType($event)"
 | 
			
		||||
            (clickStoragePath)="clickStoragePath($event)"
 | 
			
		||||
            (clickMoreLike)="clickMoreLike(d.id)">
 | 
			
		||||
          </pngx-document-card-large>
 | 
			
		||||
        }
 | 
			
		||||
      </div>
 | 
			
		||||
    }
 | 
			
		||||
    @if (list.displayMode === DisplayMode.TABLE) {
 | 
			
		||||
      <div class="table-responsive">
 | 
			
		||||
        <table class="table table-sm align-middle border shadow-sm">
 | 
			
		||||
          <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <th></th>
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.ASN)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="archive_serial_number"
 | 
			
		||||
                  title="Sort by ASN" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>ASN</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="correspondent__name"
 | 
			
		||||
                  title="Sort by correspondent" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Correspondent</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.TITLE)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="title"
 | 
			
		||||
                  title="Sort by title" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  style="min-width: 150px;"
 | 
			
		||||
                  i18n>Title</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.TAGS) && !activeDisplayFields.includes(DisplayField.TITLE)) {
 | 
			
		||||
                <th i18n>Tags</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="owner"
 | 
			
		||||
                  title="Sort by owner" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Owner</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="num_notes"
 | 
			
		||||
                  title="Sort by notes" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Notes</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="document_type__name"
 | 
			
		||||
                  title="Sort by document type" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Document type</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="storage_path__name"
 | 
			
		||||
                  title="Sort by storage path" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Storage path</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.CREATED)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="created"
 | 
			
		||||
                  title="Sort by created date" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Created</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.ADDED)) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="added"
 | 
			
		||||
                  title="Sort by added date" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)"
 | 
			
		||||
                  i18n>Added</th>
 | 
			
		||||
              }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.PAGE_COUNT)) {
 | 
			
		||||
    @if (list.error ) {
 | 
			
		||||
      <div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
 | 
			
		||||
    } @else {
 | 
			
		||||
      @if (list.displayMode === DisplayMode.LARGE_CARDS) {
 | 
			
		||||
        <div>
 | 
			
		||||
          @for (d of list.documents; track d.id) {
 | 
			
		||||
            <pngx-document-card-large
 | 
			
		||||
              [selected]="list.isSelected(d)"
 | 
			
		||||
              (toggleSelected)="toggleSelected(d, $event)"
 | 
			
		||||
              (dblClickDocument)="openDocumentDetail(d)"
 | 
			
		||||
              [document]="d"
 | 
			
		||||
              [displayFields]="activeDisplayFields"
 | 
			
		||||
              (clickTag)="clickTag($event)"
 | 
			
		||||
              (clickCorrespondent)="clickCorrespondent($event)"
 | 
			
		||||
              (clickDocumentType)="clickDocumentType($event)"
 | 
			
		||||
              (clickStoragePath)="clickStoragePath($event)"
 | 
			
		||||
              (clickMoreLike)="clickMoreLike(d.id)">
 | 
			
		||||
            </pngx-document-card-large>
 | 
			
		||||
          }
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      @if (list.displayMode === DisplayMode.TABLE) {
 | 
			
		||||
        <div class="table-responsive">
 | 
			
		||||
          <table class="table table-sm align-middle border shadow-sm">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th></th>
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.ASN)) {
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="page_count"
 | 
			
		||||
                    title="Sort by number of pages" i18n-title
 | 
			
		||||
                    pngxSortable="archive_serial_number"
 | 
			
		||||
                    title="Sort by ASN" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Pages</th>
 | 
			
		||||
                }
 | 
			
		||||
              @if (activeDisplayFields.includes(DisplayField.SHARED)) {
 | 
			
		||||
                <th i18n>
 | 
			
		||||
                  Shared
 | 
			
		||||
                </th>
 | 
			
		||||
              }
 | 
			
		||||
              @for (field_id of activeDisplayCustomFields; track field_id) {
 | 
			
		||||
                <th class="cursor-pointer"
 | 
			
		||||
                  pngxSortable="{{field_id}}"
 | 
			
		||||
                  title="Sort by {{getDisplayCustomFieldTitle(field_id)}}" i18n-title
 | 
			
		||||
                  [currentSortField]="list.sortField"
 | 
			
		||||
                  [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                  (sort)="onSort($event)">
 | 
			
		||||
                  {{getDisplayCustomFieldTitle(field_id)}}
 | 
			
		||||
                </th>
 | 
			
		||||
              }
 | 
			
		||||
            </tr>
 | 
			
		||||
          </thead>
 | 
			
		||||
          <tbody>
 | 
			
		||||
            @for (d of list.documents; track d.id) {
 | 
			
		||||
              <tr (click)="toggleSelected(d, $event); $event.stopPropagation();" (dblclick)="openDocumentDetail(d)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
 | 
			
		||||
                <td>
 | 
			
		||||
                  <div class="form-check">
 | 
			
		||||
                    <input type="checkbox" class="form-check-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event); $event.stopPropagation();">
 | 
			
		||||
                    <label class="form-check-label" for="docCheck{{d.id}}"></label>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.ASN)) {
 | 
			
		||||
                  <td class="">
 | 
			
		||||
                    {{d.archive_serial_number}}
 | 
			
		||||
                  </td>
 | 
			
		||||
                    i18n>ASN</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
 | 
			
		||||
                  <td class="">
 | 
			
		||||
                    @if (d.correspondent) {
 | 
			
		||||
                      <a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
 | 
			
		||||
                    }
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="correspondent__name"
 | 
			
		||||
                    title="Sort by correspondent" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Correspondent</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
 | 
			
		||||
                  <td width="30%">
 | 
			
		||||
                    @if (activeDisplayFields.includes(DisplayField.TITLE)) {
 | 
			
		||||
                      <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) {
 | 
			
		||||
                        <pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  </td>
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.TITLE)) {
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="title"
 | 
			
		||||
                    title="Sort by title" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    style="min-width: 150px;"
 | 
			
		||||
                    i18n>Title</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.TAGS) && !activeDisplayFields.includes(DisplayField.TITLE)) {
 | 
			
		||||
                  <th i18n>Tags</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
 | 
			
		||||
                  <td>
 | 
			
		||||
                    {{d.owner | username}}
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="owner"
 | 
			
		||||
                    title="Sort by owner" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Owner</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
 | 
			
		||||
                  <td class="">
 | 
			
		||||
                    @if (d.notes.length) {
 | 
			
		||||
                      <a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
 | 
			
		||||
                        <span class="badge rounded-pill bg-light border text-primary">
 | 
			
		||||
                          <i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
 | 
			
		||||
                        {{d.notes.length}}</span>
 | 
			
		||||
                      </a>
 | 
			
		||||
                    }
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="num_notes"
 | 
			
		||||
                    title="Sort by notes" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Notes</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
 | 
			
		||||
                  <td class="">
 | 
			
		||||
                    @if (d.document_type) {
 | 
			
		||||
                      <a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
 | 
			
		||||
                    }
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="document_type__name"
 | 
			
		||||
                    title="Sort by document type" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Document type</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
 | 
			
		||||
                  <td class="">
 | 
			
		||||
                    @if (d.storage_path) {
 | 
			
		||||
                      <a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
 | 
			
		||||
                    }
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="storage_path__name"
 | 
			
		||||
                    title="Sort by storage path" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Storage path</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.CREATED)) {
 | 
			
		||||
                  <td>
 | 
			
		||||
                    {{d.created_date | customDate}}
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="created"
 | 
			
		||||
                    title="Sort by created date" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Created</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.ADDED)) {
 | 
			
		||||
                  <td>
 | 
			
		||||
                    {{d.added | customDate}}
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="added"
 | 
			
		||||
                    title="Sort by added date" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)"
 | 
			
		||||
                    i18n>Added</th>
 | 
			
		||||
                }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.PAGE_COUNT)) {
 | 
			
		||||
                    <td>
 | 
			
		||||
                        {{ d.page_count }}
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <th class="cursor-pointer"
 | 
			
		||||
                      pngxSortable="page_count"
 | 
			
		||||
                      title="Sort by number of pages" i18n-title
 | 
			
		||||
                      [currentSortField]="list.sortField"
 | 
			
		||||
                      [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                      (sort)="onSort($event)"
 | 
			
		||||
                      i18n>Pages</th>
 | 
			
		||||
                  }
 | 
			
		||||
                @if (activeDisplayFields.includes(DisplayField.SHARED)) {
 | 
			
		||||
                  <td>
 | 
			
		||||
                    @if (d.is_shared_by_requester) { <ng-container i18n>Yes</ng-container> } @else { <ng-container i18n>No</ng-container> }
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <th i18n>
 | 
			
		||||
                    Shared
 | 
			
		||||
                  </th>
 | 
			
		||||
                }
 | 
			
		||||
                @for (field of activeDisplayCustomFields; track field) {
 | 
			
		||||
                  <td class="">
 | 
			
		||||
                    <pngx-custom-field-display [document]="d" [fieldDisplayKey]="field"></pngx-custom-field-display>
 | 
			
		||||
                  </td>
 | 
			
		||||
                @for (field_id of activeDisplayCustomFields; track field_id) {
 | 
			
		||||
                  <th class="cursor-pointer"
 | 
			
		||||
                    pngxSortable="{{field_id}}"
 | 
			
		||||
                    title="Sort by {{getDisplayCustomFieldTitle(field_id)}}" i18n-title
 | 
			
		||||
                    [currentSortField]="list.sortField"
 | 
			
		||||
                    [currentSortReverse]="list.sortReverse"
 | 
			
		||||
                    (sort)="onSort($event)">
 | 
			
		||||
                    {{getDisplayCustomFieldTitle(field_id)}}
 | 
			
		||||
                  </th>
 | 
			
		||||
                }
 | 
			
		||||
              </tr>
 | 
			
		||||
            }
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
      </div>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              @for (d of list.documents; track d.id) {
 | 
			
		||||
                <tr (click)="toggleSelected(d, $event); $event.stopPropagation();" (dblclick)="openDocumentDetail(d)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <div class="form-check">
 | 
			
		||||
                      <input type="checkbox" class="form-check-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event); $event.stopPropagation();">
 | 
			
		||||
                      <label class="form-check-label" for="docCheck{{d.id}}"></label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </td>
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.ASN)) {
 | 
			
		||||
                    <td class="">
 | 
			
		||||
                      {{d.archive_serial_number}}
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
 | 
			
		||||
                    <td class="">
 | 
			
		||||
                      @if (d.correspondent) {
 | 
			
		||||
                        <a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
 | 
			
		||||
                      }
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
 | 
			
		||||
                    <td width="30%">
 | 
			
		||||
                      @if (activeDisplayFields.includes(DisplayField.TITLE)) {
 | 
			
		||||
                        <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) {
 | 
			
		||||
                          <pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
 | 
			
		||||
                    <td>
 | 
			
		||||
                      {{d.owner | username}}
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
 | 
			
		||||
                    <td class="">
 | 
			
		||||
                      @if (d.notes.length) {
 | 
			
		||||
                        <a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
 | 
			
		||||
                          <span class="badge rounded-pill bg-light border text-primary">
 | 
			
		||||
                            <i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
 | 
			
		||||
                          {{d.notes.length}}</span>
 | 
			
		||||
                        </a>
 | 
			
		||||
                      }
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
 | 
			
		||||
                    <td class="">
 | 
			
		||||
                      @if (d.document_type) {
 | 
			
		||||
                        <a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
 | 
			
		||||
                      }
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
 | 
			
		||||
                    <td class="">
 | 
			
		||||
                      @if (d.storage_path) {
 | 
			
		||||
                        <a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
 | 
			
		||||
                      }
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.CREATED)) {
 | 
			
		||||
                    <td>
 | 
			
		||||
                      {{d.created_date | customDate}}
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.ADDED)) {
 | 
			
		||||
                    <td>
 | 
			
		||||
                      {{d.added | customDate}}
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.PAGE_COUNT)) {
 | 
			
		||||
                      <td>
 | 
			
		||||
                          {{ d.page_count }}
 | 
			
		||||
                      </td>
 | 
			
		||||
                    }
 | 
			
		||||
                  @if (activeDisplayFields.includes(DisplayField.SHARED)) {
 | 
			
		||||
                    <td>
 | 
			
		||||
                      @if (d.is_shared_by_requester) { <ng-container i18n>Yes</ng-container> } @else { <ng-container i18n>No</ng-container> }
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                  @for (field of activeDisplayCustomFields; track field) {
 | 
			
		||||
                    <td class="">
 | 
			
		||||
                      <pngx-custom-field-display [document]="d" [fieldDisplayKey]="field"></pngx-custom-field-display>
 | 
			
		||||
                    </td>
 | 
			
		||||
                  }
 | 
			
		||||
                </tr>
 | 
			
		||||
              }
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      @if (list.displayMode === DisplayMode.SMALL_CARDS) {
 | 
			
		||||
        <div class="row row-cols-paperless-cards">
 | 
			
		||||
          @for (d of list.documents; track d.id) {
 | 
			
		||||
            <pngx-document-card-small class="p-0"
 | 
			
		||||
              [selected]="list.isSelected(d)"
 | 
			
		||||
              (toggleSelected)="toggleSelected(d, $event)"
 | 
			
		||||
              (dblClickDocument)="openDocumentDetail(d)"
 | 
			
		||||
              [document]="d"
 | 
			
		||||
              (clickTag)="clickTag($event)"
 | 
			
		||||
              [displayFields]="activeDisplayFields"
 | 
			
		||||
              (clickCorrespondent)="clickCorrespondent($event)"
 | 
			
		||||
              (clickStoragePath)="clickStoragePath($event)"
 | 
			
		||||
              (clickDocumentType)="clickDocumentType($event)">
 | 
			
		||||
            </pngx-document-card-small>
 | 
			
		||||
          }
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      @if (list.documents?.length > 15) {
 | 
			
		||||
        <div class="mt-3">
 | 
			
		||||
          <ng-container *ngTemplateOutlet="pagination"></ng-container>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    @if (list.displayMode === DisplayMode.SMALL_CARDS) {
 | 
			
		||||
      <div class="row row-cols-paperless-cards">
 | 
			
		||||
        @for (d of list.documents; track d.id) {
 | 
			
		||||
          <pngx-document-card-small class="p-0"
 | 
			
		||||
            [selected]="list.isSelected(d)"
 | 
			
		||||
            (toggleSelected)="toggleSelected(d, $event)"
 | 
			
		||||
            (dblClickDocument)="openDocumentDetail(d)"
 | 
			
		||||
            [document]="d"
 | 
			
		||||
            (clickTag)="clickTag($event)"
 | 
			
		||||
            [displayFields]="activeDisplayFields"
 | 
			
		||||
            (clickCorrespondent)="clickCorrespondent($event)"
 | 
			
		||||
            (clickStoragePath)="clickStoragePath($event)"
 | 
			
		||||
            (clickDocumentType)="clickDocumentType($event)">
 | 
			
		||||
          </pngx-document-card-small>
 | 
			
		||||
        }
 | 
			
		||||
  </div>
 | 
			
		||||
  @if (list.showPreviewPane) {
 | 
			
		||||
    <div class="col-lg-6">
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="btn-toolbar mb-1 border-bottom align-items-center">
 | 
			
		||||
          <div class="btn-group pb-3">
 | 
			
		||||
            <button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="list.documents.length === 0 || !hasPrevious">
 | 
			
		||||
              <i-bs width="1.2em" height="1.2em" name="arrow-left" class="me-1"></i-bs><ng-container i18n>Previous</ng-container>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button type="button" class="btn btn-sm btn-outline-secondary"  i18n-title title="Next" (click)="nextDoc()" [disabled]="list.documents.length === 0 || !hasNext">
 | 
			
		||||
              <ng-container i18n>Next</ng-container><i-bs width="1.2em" height="1.2em" name="arrow-right" class="ms-1"></i-bs>
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="input-group pb-3 ms-auto">
 | 
			
		||||
            <h5 class="mb-0">
 | 
			
		||||
              {{list.firstSelectedDocument?.title}}
 | 
			
		||||
            </h5>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    }
 | 
			
		||||
    @if (list.documents?.length > 15) {
 | 
			
		||||
      <div class="mt-3">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="pagination"></ng-container>
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="col preview-pane">
 | 
			
		||||
          @if (list.selected.size > 0) {
 | 
			
		||||
            <pngx-preview-popup [document]="list.firstSelectedDocument" [previewOnly]="true"></pngx-preview-popup>
 | 
			
		||||
          } @else {
 | 
			
		||||
            <div class="w-100 h-100 position-relative">
 | 
			
		||||
              <p class="fst-italic">
 | 
			
		||||
                <ng-container i18n>No document selected</ng-container>
 | 
			
		||||
              </p>
 | 
			
		||||
            </div>
 | 
			
		||||
          }
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    }
 | 
			
		||||
    </div>
 | 
			
		||||
  }
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -80,3 +80,9 @@ a {
 | 
			
		||||
pngx-page-header .dropdown-menu {
 | 
			
		||||
  --bs-dropdown-min-width: 12em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.preview-pane {
 | 
			
		||||
  height: 60rem;
 | 
			
		||||
  top: 70px;
 | 
			
		||||
  position: sticky;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -326,24 +326,36 @@ export class DocumentListComponent
 | 
			
		||||
    this.hotKeyService
 | 
			
		||||
      .addShortcut({
 | 
			
		||||
        keys: 'control.arrowleft',
 | 
			
		||||
        description: $localize`Previous page`,
 | 
			
		||||
        description: $localize`Previous page / document`,
 | 
			
		||||
      })
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe(() => {
 | 
			
		||||
        if (this.list.currentPage > 1) {
 | 
			
		||||
          this.list.currentPage--
 | 
			
		||||
        if (this.list.showPreviewPane) {
 | 
			
		||||
          if (this.hasPrevious) {
 | 
			
		||||
            this.previousDoc()
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          if (this.list.currentPage > 1) {
 | 
			
		||||
            this.list.currentPage--
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    this.hotKeyService
 | 
			
		||||
      .addShortcut({
 | 
			
		||||
        keys: 'control.arrowright',
 | 
			
		||||
        description: $localize`Next page`,
 | 
			
		||||
        description: $localize`Next page / document`,
 | 
			
		||||
      })
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe(() => {
 | 
			
		||||
        if (this.list.currentPage < this.list.getLastPage()) {
 | 
			
		||||
          this.list.currentPage++
 | 
			
		||||
        if (this.list.showPreviewPane) {
 | 
			
		||||
          if (this.hasNext) {
 | 
			
		||||
            this.nextDoc()
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          if (this.list.currentPage < this.list.getLastPage()) {
 | 
			
		||||
            this.list.currentPage++
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
@@ -473,4 +485,45 @@ export class DocumentListComponent
 | 
			
		||||
  resetFilters() {
 | 
			
		||||
    this.filterEditor.resetSelected()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get hasPrevious(): boolean {
 | 
			
		||||
    return (
 | 
			
		||||
      (this.list.selected.size > 0 &&
 | 
			
		||||
        this.list.documents.indexOf(this.list.firstSelectedDocument) > 0) ||
 | 
			
		||||
      (this.list.selected.size === 0 && this.list.documents.length > 0)
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get hasNext(): boolean {
 | 
			
		||||
    return (
 | 
			
		||||
      (this.list.selected.size > 0 &&
 | 
			
		||||
        this.list.documents.indexOf(this.list.firstSelectedDocument) <
 | 
			
		||||
          this.list.documents.length - 1) ||
 | 
			
		||||
      (this.list.selected.size === 0 && this.list.documents.length > 0)
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public nextDoc(): void {
 | 
			
		||||
    const index =
 | 
			
		||||
      this.list.selected.size === 0
 | 
			
		||||
        ? 0
 | 
			
		||||
        : Math.min(
 | 
			
		||||
            this.list.documents.indexOf(this.list.firstSelectedDocument) + 1,
 | 
			
		||||
            this.list.documents.length - 1
 | 
			
		||||
          )
 | 
			
		||||
    this.list.selected.clear()
 | 
			
		||||
    this.list.selected.add(this.list.documents[index].id)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public previousDoc(): void {
 | 
			
		||||
    const index =
 | 
			
		||||
      this.list.selected.size === 0
 | 
			
		||||
        ? 0
 | 
			
		||||
        : Math.max(
 | 
			
		||||
            this.list.documents.indexOf(this.list.firstSelectedDocument) - 1,
 | 
			
		||||
            0
 | 
			
		||||
          )
 | 
			
		||||
    this.list.selected.clear()
 | 
			
		||||
    this.list.selected.add(this.list.documents[index].id)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,11 @@ export interface ListViewState {
 | 
			
		||||
   * The fields to display in the document list.
 | 
			
		||||
   */
 | 
			
		||||
  displayFields?: DisplayField[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether the preview pane is shown.
 | 
			
		||||
   */
 | 
			
		||||
  showPreviewPane?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -165,6 +170,7 @@ export class DocumentListViewService {
 | 
			
		||||
      sortReverse: true,
 | 
			
		||||
      filterRules: [],
 | 
			
		||||
      selected: new Set<number>(),
 | 
			
		||||
      showPreviewPane: false,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -451,6 +457,15 @@ export class DocumentListViewService {
 | 
			
		||||
    this.saveDocumentListView()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get showPreviewPane(): boolean {
 | 
			
		||||
    return this.activeListViewState.showPreviewPane
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set showPreviewPane(show: boolean) {
 | 
			
		||||
    this.activeListViewState.showPreviewPane = show
 | 
			
		||||
    this.saveDocumentListView()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private saveDocumentListView() {
 | 
			
		||||
    if (this._activeSavedViewId == null) {
 | 
			
		||||
      let savedState: ListViewState = {
 | 
			
		||||
@@ -461,6 +476,7 @@ export class DocumentListViewService {
 | 
			
		||||
        sortReverse: this.activeListViewState.sortReverse,
 | 
			
		||||
        displayMode: this.activeListViewState.displayMode,
 | 
			
		||||
        displayFields: this.activeListViewState.displayFields,
 | 
			
		||||
        showPreviewPane: this.activeListViewState.showPreviewPane,
 | 
			
		||||
      }
 | 
			
		||||
      localStorage.setItem(
 | 
			
		||||
        DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
 | 
			
		||||
@@ -626,4 +642,8 @@ export class DocumentListViewService {
 | 
			
		||||
  documentIndexInCurrentView(documentID: number): number {
 | 
			
		||||
    return this.documents.map((d) => d.id).indexOf(documentID)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get firstSelectedDocument(): Document {
 | 
			
		||||
    return this.documents.find((d) => this.selected.has(d.id))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,7 @@ import {
 | 
			
		||||
  trash,
 | 
			
		||||
  uiRadios,
 | 
			
		||||
  upcScan,
 | 
			
		||||
  windowSplit,
 | 
			
		||||
  windowStack,
 | 
			
		||||
  x,
 | 
			
		||||
  xCircle,
 | 
			
		||||
@@ -323,6 +324,7 @@ const icons = {
 | 
			
		||||
  trash,
 | 
			
		||||
  uiRadios,
 | 
			
		||||
  upcScan,
 | 
			
		||||
  windowSplit,
 | 
			
		||||
  windowStack,
 | 
			
		||||
  x,
 | 
			
		||||
  xCircle,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user