]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: doc detail component fixes (#5373)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Sat, 13 Jan 2024 21:40:22 +0000 (13:40 -0800)
committerGitHub <noreply@github.com>
Sat, 13 Jan 2024 21:40:22 +0000 (21:40 +0000)
src-ui/messages.xlf
src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts
src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.spec.ts
src-ui/src/app/components/document-detail/document-detail.component.ts

index a3e9b3330702155cf630760a15cfbe81a9622806..b04f86ebc3fc801f23ae51d6099ad972bfe44eee 100644 (file)
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">701</context>
+          <context context-type="linenumber">716</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">703</context>
+          <context context-type="linenumber">718</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6581372518205328477" datatype="html">
-        <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="appTitle"/></source>
+        <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="environment.appTitle"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">38</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2901300640157872718" datatype="html">
-        <source>Welcome to <x id="PH" equiv-text="appTitle"/></source>
+        <source>Welcome to <x id="PH" equiv-text="environment.appTitle"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">43</context>
+          <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1325877348738783391" datatype="html">
         <source>Dashboard updated</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">74</context>
+          <context context-type="linenumber">71</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3214475953924351473" datatype="html">
         <source>Error updating dashboard</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">77</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2946624699882754313" datatype="html">
         </context-group>
       </trans-unit>
       <trans-unit id="7206723502037428235" datatype="html">
-        <source>Notes <x id="START_BLOCK_IF" equiv-text="@if (document?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="}&lt;/a&gt;"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
+        <source>Notes <x id="START_BLOCK_IF" equiv-text="@if (document?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
           <context context-type="linenumber">286,289</context>
         <source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">276,278</context>
+          <context context-type="linenumber">284,286</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3200733026060976258" datatype="html">
         <source>Document changes detected</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">298</context>
+          <context context-type="linenumber">307</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2887155916749964" datatype="html">
         <source>The version of this document in your browser session appears older than the existing version.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">299</context>
+          <context context-type="linenumber">308</context>
         </context-group>
       </trans-unit>
       <trans-unit id="237142428785956348" datatype="html">
         <source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">300</context>
+          <context context-type="linenumber">309</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8720977247725652816" datatype="html">
         <source>Ok</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">301</context>
+          <context context-type="linenumber">311</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5758784066858623886" datatype="html">
         <source>Error retrieving metadata</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">437</context>
+          <context context-type="linenumber">448</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3456881259945295697" datatype="html">
         <source>Error retrieving suggestions.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">458</context>
+          <context context-type="linenumber">473</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8348337312757497317" datatype="html">
         <source>Document saved successfully.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">576</context>
+          <context context-type="linenumber">591</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">585</context>
+          <context context-type="linenumber">600</context>
         </context-group>
       </trans-unit>
       <trans-unit id="448882439049417053" datatype="html">
         <source>Error saving document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">589</context>
+          <context context-type="linenumber">604</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">630</context>
+          <context context-type="linenumber">645</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9021887951960049161" datatype="html">
         <source>Confirm delete</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">656</context>
+          <context context-type="linenumber">671</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
         <source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">657</context>
+          <context context-type="linenumber">672</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6691075929777935948" datatype="html">
         <source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">658</context>
+          <context context-type="linenumber">673</context>
         </context-group>
       </trans-unit>
       <trans-unit id="719892092227206532" datatype="html">
         <source>Delete document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">660</context>
+          <context context-type="linenumber">675</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7295637485862454066" datatype="html">
         <source>Error deleting document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">679</context>
+          <context context-type="linenumber">694</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7362691899087997122" datatype="html">
         <source>Redo OCR confirm</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">699</context>
+          <context context-type="linenumber">714</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
         <source>This operation will permanently redo OCR for this document.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">700</context>
+          <context context-type="linenumber">715</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5729001209753056399" datatype="html">
         <source>Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">711</context>
+          <context context-type="linenumber">726</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4409560272830824468" datatype="html">
         <source>Error executing operation</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">722</context>
+          <context context-type="linenumber">737</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4458954481601077369" datatype="html">
         <source>Page Fit</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
-          <context context-type="linenumber">791</context>
+          <context context-type="linenumber">806</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6857598786757174736" datatype="html">
         <source>Successfully completed one-time migratration of settings to the database!</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">468</context>
+          <context context-type="linenumber">471</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5558341108007064934" datatype="html">
         <source>Unable to migrate settings to the database, please try saving manually.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">469</context>
+          <context context-type="linenumber">472</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1168781785897678748" datatype="html">
         <source>You can restart the tour from the settings page.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">539</context>
+          <context context-type="linenumber">542</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3852289441366561594" datatype="html">
index 472fcab6ae677c6bd240f97b13a7069be371eb8d..274b06032341fe93cd0f4b8ac8db84d0a71f418d 100644 (file)
@@ -465,33 +465,42 @@ export class PdfViewerComponent
 
     this.clear()
 
+    if (this.pdfViewer) {
+      this.pdfViewer._resetView()
+      this.pdfViewer = null
+    }
+
     this.setupViewer()
 
-    this.loadingTask = PDFJS.getDocument(this.getDocumentParams())
+    try {
+      this.loadingTask = PDFJS.getDocument(this.getDocumentParams())
 
-    this.loadingTask!.onProgress = (progressData: PDFProgressData) => {
-      this.onProgress.emit(progressData)
-    }
+      this.loadingTask!.onProgress = (progressData: PDFProgressData) => {
+        this.onProgress.emit(progressData)
+      }
 
-    const src = this.src
+      const src = this.src
 
-    from(this.loadingTask!.promise as Promise<PDFDocumentProxy>)
-      .pipe(takeUntil(this.destroy$))
-      .subscribe({
-        next: (pdf) => {
-          this._pdf = pdf
-          this.lastLoaded = src
+      from(this.loadingTask!.promise as Promise<PDFDocumentProxy>)
+        .pipe(takeUntil(this.destroy$))
+        .subscribe({
+          next: (pdf) => {
+            this._pdf = pdf
+            this.lastLoaded = src
 
-          this.afterLoadComplete.emit(pdf)
-          this.resetPdfDocument()
+            this.afterLoadComplete.emit(pdf)
+            this.resetPdfDocument()
 
-          this.update()
-        },
-        error: (error) => {
-          this.lastLoaded = null
-          this.onError.emit(error)
-        },
-      })
+            this.update()
+          },
+          error: (error) => {
+            this.lastLoaded = null
+            this.onError.emit(error)
+          },
+        })
+    } catch (e) {
+      this.onError.emit(e)
+    }
   }
 
   private update() {
index 5000af93d1e8604c3c57e5c416e678d3c1095868..eaf7d298a3b4d0b0d631f553f7cd4166e5a890f5 100644 (file)
   <button type="button" class="btn btn-sm btn-outline-danger me-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
     <svg class="buttonicon" fill="currentColor">
       <use xlink:href="assets/bootstrap-icons.svg#trash" />
-      </svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
-    </button>
+    </svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
+  </button>
 
-    <div class="btn-group me-2">
-      <a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
-        <svg class="buttonicon me-md-1" fill="currentColor">
+  <div class="btn-group me-2">
+    <a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
+      <svg class="buttonicon me-md-1" fill="currentColor">
           <use xlink:href="assets/bootstrap-icons.svg#download" />
-          </svg><span class="d-none d-lg-inline ps-1" i18n>Download</span>
-        </a>
+      </svg><span class="d-none d-lg-inline ps-1" i18n>Download</span>
+    </a>
 
-        @if (metadata?.has_archive_version) {
-          <div class="btn-group" ngbDropdown role="group">
-            <button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
-            <div class="dropdown-menu shadow" ngbDropdownMenu>
-              <a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
-            </div>
-          </div>
-        }
+    @if (metadata?.has_archive_version) {
+      <div class="btn-group" ngbDropdown role="group">
+        <button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
+        <div class="dropdown-menu shadow" ngbDropdownMenu>
+          <a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
+        </div>
       </div>
+    }
+  </div>
 
-      <div class="ms-auto" ngbDropdown>
-        <button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle>
-          <svg class="toolbaricon" fill="currentColor">
-            <use xlink:href="assets/bootstrap-icons.svg#three-dots" />
-          </svg>
-          <div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Actions</ng-container></div>
-        </button>
-        <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
-          <button ngbDropdownItem (click)="redoOcr()" [disabled]="!userCanEdit">
-            <svg class="buttonicon-sm" fill="currentColor">
-              <use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
-              </svg><span class="ps-1" i18n>Redo OCR</span>
-            </button>
-
-            <button ngbDropdownItem (click)="moreLike()">
-              <svg class="buttonicon-sm" fill="currentColor">
-                <use xlink:href="assets/bootstrap-icons.svg#diagram-3" />
-                </svg><span class="ps-1" i18n>More like this</span>
-              </button>
-            </div>
-          </div>
-
-          <pngx-custom-fields-dropdown
-            *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
-            class="me-2"
-            [documentId]="documentId"
-            [disabled]="!userIsOwner"
-            [existingFields]="document?.custom_fields"
-            (created)="refreshCustomFields()"
-            (added)="addField($event)">
-          </pngx-custom-fields-dropdown>
-
-          <pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
-        </pngx-page-header>
-
-        <div class="row">
-          <div class="col-md-6 col-xl-4 mb-4">
+  <div class="ms-auto" ngbDropdown>
+    <button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle>
+      <svg class="toolbaricon" fill="currentColor">
+        <use xlink:href="assets/bootstrap-icons.svg#three-dots" />
+      </svg>
+      <div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Actions</ng-container></div>
+    </button>
+    <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
+      <button ngbDropdownItem (click)="redoOcr()" [disabled]="!userCanEdit">
+        <svg class="buttonicon-sm" fill="currentColor">
+          <use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
+        </svg><span class="ps-1" i18n>Redo OCR</span>
+      </button>
 
-            <form [formGroup]='documentForm' (ngSubmit)="save()">
+      <button ngbDropdownItem (click)="moreLike()">
+        <svg class="buttonicon-sm" fill="currentColor">
+            <use xlink:href="assets/bootstrap-icons.svg#diagram-3" />
+        </svg><span class="ps-1" i18n>More like this</span>
+      </button>
+    </div>
+  </div>
 
-              <div class="btn-toolbar mb-1 pb-3 border-bottom">
-                <div class="btn-group">
-                  <button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
-                    <svg class="buttonicon" fill="currentColor">
-                      <use xlink:href="assets/bootstrap-icons.svg#x" />
-                    </svg>
-                  </button>
-                  <button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="!hasPrevious()">
-                    <svg class="buttonicon" fill="currentColor">
-                      <use xlink:href="assets/bootstrap-icons.svg#arrow-left" />
-                    </svg>
-                  </button>
-                  <button type="button" class="btn btn-sm btn-outline-secondary"  i18n-title title="Next" (click)="nextDoc()" [disabled]="!hasNext()">
-                    <svg class="buttonicon" fill="currentColor">
-                      <use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
-                    </svg>
-                  </button>
-                </div>
+  <pngx-custom-fields-dropdown
+    *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
+    class="me-2"
+    [documentId]="documentId"
+    [disabled]="!userIsOwner"
+    [existingFields]="document?.custom_fields"
+    (created)="refreshCustomFields()"
+    (added)="addField($event)">
+  </pngx-custom-fields-dropdown>
 
-                <ng-container *ngTemplateOutlet="saveButtons"></ng-container>
-              </div>
+  <pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
+</pngx-page-header>
 
-              <ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
-                <li [ngbNavItem]="DocumentDetailNavIDs.Details">
-                  <a ngbNavLink i18n>Details</a>
-                  <ng-template ngbNavContent>
-                    <div>
-                      <pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
-                      <pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
-                      <pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
-                      [error]="error?.created_date"></pngx-input-date>
-                      <pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
-                      (createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
-                      <pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
-                      (createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }"></pngx-input-select>
-                      <pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
-                      (createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
-                      <pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
-                      @for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) {
-                        <div [formGroup]="customFieldFormFields.controls[i]">
-                          @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
-                            @case (PaperlessCustomFieldDataType.String) {
-                              <pngx-input-text formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [error]="getCustomFieldError(i)"></pngx-input-text>
-                            }
-                            @case (PaperlessCustomFieldDataType.Date) {
-                              <pngx-input-date formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [error]="getCustomFieldError(i)"></pngx-input-date>
-                            }
-                            @case (PaperlessCustomFieldDataType.Integer) {
-                              <pngx-input-number formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [showAdd]="false"
-                              [error]="getCustomFieldError(i)"></pngx-input-number>
-                            }
-                            @case (PaperlessCustomFieldDataType.Float) {
-                              <pngx-input-number formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [showAdd]="false"
-                              [step]=".1"
-                              [error]="getCustomFieldError(i)"></pngx-input-number>
-                            }
-                            @case (PaperlessCustomFieldDataType.Monetary) {
-                              <pngx-input-number formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [showAdd]="false"
-                              [step]=".01"
-                              [error]="getCustomFieldError(i)"></pngx-input-number>
-                            }
-                            @case (PaperlessCustomFieldDataType.Boolean) {
-                              <pngx-input-check formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"></pngx-input-check>
-                            }
-                            @case (PaperlessCustomFieldDataType.Url) {
-                              <pngx-input-url formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [error]="getCustomFieldError(i)"></pngx-input-url>
-                            }
-                            @case (PaperlessCustomFieldDataType.DocumentLink) {
-                              <pngx-input-document-link formControlName="value"
-                              [title]="getCustomFieldFromInstance(fieldInstance)?.name"
-                              [parentDocumentID]="documentId"
-                              [removable]="userIsOwner"
-                              (removed)="removeField(fieldInstance)"
-                              [horizontal]="true"
-                              [error]="getCustomFieldError(i)"></pngx-input-document-link>
-                            }
-                          }
-                        </div>
-                      }
-                    </div>
+<div class="row">
+  <div class="col-md-6 col-xl-4 mb-4">
 
-                    <div class="d-flex border-top pt-3">
-                      <ng-container *ngTemplateOutlet="saveButtons"></ng-container>
-                    </div>
-                  </ng-template>
-                </li>
+    <form [formGroup]='documentForm' (ngSubmit)="save()">
 
-                <li [ngbNavItem]="DocumentDetailNavIDs.Content">
-                  <a ngbNavLink i18n>Content</a>
-                  <ng-template ngbNavContent>
-                    <div>
-                      <textarea class="form-control" id="content" rows="20" formControlName='content' [class.rtl]="isRTL"></textarea>
-                    </div>
-                  </ng-template>
-                </li>
+      <div class="btn-toolbar mb-1 pb-3 border-bottom">
+        <div class="btn-group">
+          <button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
+            <svg class="buttonicon" fill="currentColor">
+              <use xlink:href="assets/bootstrap-icons.svg#x" />
+            </svg>
+          </button>
+          <button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="!hasPrevious()">
+            <svg class="buttonicon" fill="currentColor">
+              <use xlink:href="assets/bootstrap-icons.svg#arrow-left" />
+            </svg>
+          </button>
+          <button type="button" class="btn btn-sm btn-outline-secondary"  i18n-title title="Next" (click)="nextDoc()" [disabled]="!hasNext()">
+            <svg class="buttonicon" fill="currentColor">
+              <use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
+            </svg>
+          </button>
+        </div>
 
-                <li [ngbNavItem]="DocumentDetailNavIDs.Metadata">
-                  <a ngbNavLink i18n>Metadata</a>
-                  <ng-template ngbNavContent>
+        <ng-container *ngTemplateOutlet="saveButtons"></ng-container>
+      </div>
 
-                    @if (document) {
-                      <table class="table table-borderless">
-                        <tbody>
-                          <tr>
-                            <td i18n>Date modified</td>
-                            <td>{{document.modified | customDate}}</td>
-                          </tr>
-                          <tr>
-                            <td i18n>Date added</td>
-                            <td>{{document.added | customDate}}</td>
-                          </tr>
-                          <tr>
-                            <td i18n>Media filename</td>
-                            <td>{{metadata?.media_filename}}</td>
-                          </tr>
-                          <tr>
-                            <td i18n>Original filename</td>
-                            <td>{{metadata?.original_filename}}</td>
-                          </tr>
-                          <tr>
-                            <td i18n>Original MD5 checksum</td>
-                            <td>{{metadata?.original_checksum}}</td>
-                          </tr>
-                          <tr>
-                            <td i18n>Original file size</td>
-                            <td>{{metadata?.original_size | fileSize}}</td>
-                          </tr>
-                          <tr>
-                            <td i18n>Original mime type</td>
-                            <td>{{metadata?.original_mime_type}}</td>
-                          </tr>
-                          @if (metadata?.has_archive_version) {
-                            <tr>
-                              <td i18n>Archive MD5 checksum</td>
-                              <td>{{metadata?.archive_checksum}}</td>
-                            </tr>
-                          }
-                          @if (metadata?.has_archive_version) {
-                            <tr>
-                              <td i18n>Archive file size</td>
-                              <td>{{metadata?.archive_size | fileSize}}</td>
-                            </tr>
-                          }
-                        </tbody>
-                      </table>
+      <ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
+        <li [ngbNavItem]="DocumentDetailNavIDs.Details">
+          <a ngbNavLink i18n>Details</a>
+          <ng-template ngbNavContent>
+            <div>
+              <pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
+              <pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
+              <pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
+              [error]="error?.created_date"></pngx-input-date>
+              <pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
+              (createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
+              <pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
+              (createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }"></pngx-input-select>
+              <pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
+              (createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
+              <pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
+              @for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) {
+                <div [formGroup]="customFieldFormFields.controls[i]">
+                  @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
+                    @case (PaperlessCustomFieldDataType.String) {
+                      <pngx-input-text formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [error]="getCustomFieldError(i)"></pngx-input-text>
                     }
-
-                    @if (metadata?.original_metadata?.length > 0) {
-                      <pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata.original_metadata"></pngx-metadata-collapse>
+                    @case (PaperlessCustomFieldDataType.Date) {
+                      <pngx-input-date formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [error]="getCustomFieldError(i)"></pngx-input-date>
                     }
-                    @if (metadata?.archive_metadata?.length > 0) {
-                      <pngx-metadata-collapse i18n-title title="Archived document metadata" [metadata]="metadata.archive_metadata"></pngx-metadata-collapse>
+                    @case (PaperlessCustomFieldDataType.Integer) {
+                      <pngx-input-number formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [showAdd]="false"
+                      [error]="getCustomFieldError(i)"></pngx-input-number>
+                    }
+                    @case (PaperlessCustomFieldDataType.Float) {
+                      <pngx-input-number formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [showAdd]="false"
+                      [step]=".1"
+                      [error]="getCustomFieldError(i)"></pngx-input-number>
+                    }
+                    @case (PaperlessCustomFieldDataType.Monetary) {
+                      <pngx-input-number formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [showAdd]="false"
+                      [step]=".01"
+                      [error]="getCustomFieldError(i)"></pngx-input-number>
+                    }
+                    @case (PaperlessCustomFieldDataType.Boolean) {
+                      <pngx-input-check formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"></pngx-input-check>
+                    }
+                    @case (PaperlessCustomFieldDataType.Url) {
+                      <pngx-input-url formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [error]="getCustomFieldError(i)"></pngx-input-url>
+                    }
+                    @case (PaperlessCustomFieldDataType.DocumentLink) {
+                      <pngx-input-document-link formControlName="value"
+                      [title]="getCustomFieldFromInstance(fieldInstance)?.name"
+                      [parentDocumentID]="documentId"
+                      [removable]="userIsOwner"
+                      (removed)="removeField(fieldInstance)"
+                      [horizontal]="true"
+                      [error]="getCustomFieldError(i)"></pngx-input-document-link>
                     }
-
-                  </ng-template>
-                </li>
-
-                <li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
-                  <a ngbNavLink i18n>Preview</a>
-                  @if (!pdfPreview.offsetParent) {
-                    <ng-template ngbNavContent>
-                      <ng-container *ngTemplateOutlet="previewContent"></ng-container>
-                    </ng-template>
                   }
-                </li>
+                </div>
+              }
+            </div>
 
-                @if (notesEnabled) {
-                  <li [ngbNavItem]="DocumentDetailNavIDs.Notes">
-                    <a class="text-nowrap" ngbNavLink i18n>Notes @if (document?.notes.length) {
-<span class="badge text-bg-secondary ms-1">{{document.notes.length}}</span>
-}</a>
-                    <ng-template ngbNavContent>
-                      <pngx-document-notes [documentId]="documentId" [notes]="document?.notes" [addDisabled]="!userCanEdit" (updated)="notesUpdated($event)"></pngx-document-notes>
-                    </ng-template>
-                  </li>
-                }
+            <div class="d-flex border-top pt-3">
+              <ng-container *ngTemplateOutlet="saveButtons"></ng-container>
+            </div>
+          </ng-template>
+        </li>
 
-                @if (showPermissions) {
-                  <li [ngbNavItem]="DocumentDetailNavIDs.Permissions">
-                    <a ngbNavLink i18n>Permissions</a>
-                    <ng-template ngbNavContent>
-                      <div class="mb-3">
-                        <pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
-                      </div>
-                    </ng-template>
-                  </li>
-                }
-              </ul>
+        <li [ngbNavItem]="DocumentDetailNavIDs.Content">
+          <a ngbNavLink i18n>Content</a>
+          <ng-template ngbNavContent>
+            <div>
+              <textarea class="form-control" id="content" rows="20" formControlName='content' [class.rtl]="isRTL"></textarea>
+            </div>
+          </ng-template>
+        </li>
 
-              <div [ngbNavOutlet]="nav" class="mt-3"></div>
+        <li [ngbNavItem]="DocumentDetailNavIDs.Metadata">
+          <a ngbNavLink i18n>Metadata</a>
+          <ng-template ngbNavContent>
 
-            </form>
-          </div>
+            @if (document) {
+              <table class="table table-borderless">
+                <tbody>
+                  <tr>
+                    <td i18n>Date modified</td>
+                    <td>{{document.modified | customDate}}</td>
+                  </tr>
+                  <tr>
+                    <td i18n>Date added</td>
+                    <td>{{document.added | customDate}}</td>
+                  </tr>
+                  <tr>
+                    <td i18n>Media filename</td>
+                    <td>{{metadata?.media_filename}}</td>
+                  </tr>
+                  <tr>
+                    <td i18n>Original filename</td>
+                    <td>{{metadata?.original_filename}}</td>
+                  </tr>
+                  <tr>
+                    <td i18n>Original MD5 checksum</td>
+                    <td>{{metadata?.original_checksum}}</td>
+                  </tr>
+                  <tr>
+                    <td i18n>Original file size</td>
+                    <td>{{metadata?.original_size | fileSize}}</td>
+                  </tr>
+                  <tr>
+                    <td i18n>Original mime type</td>
+                    <td>{{metadata?.original_mime_type}}</td>
+                  </tr>
+                  @if (metadata?.has_archive_version) {
+                    <tr>
+                      <td i18n>Archive MD5 checksum</td>
+                      <td>{{metadata?.archive_checksum}}</td>
+                    </tr>
+                  }
+                  @if (metadata?.has_archive_version) {
+                    <tr>
+                      <td i18n>Archive file size</td>
+                      <td>{{metadata?.archive_size | fileSize}}</td>
+                    </tr>
+                  }
+                </tbody>
+              </table>
+            }
 
-          <div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
-            <ng-container *ngTemplateOutlet="previewContent"></ng-container>
-            @if (renderAsPlainText) {
-              <div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
+            @if (metadata?.original_metadata?.length > 0) {
+              <pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata.original_metadata"></pngx-metadata-collapse>
             }
-            @if (requiresPassword) {
-              <div class="password-prompt">
-                <form>
-                  <input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
-                </form>
-              </div>
+            @if (metadata?.archive_metadata?.length > 0) {
+              <pngx-metadata-collapse i18n-title title="Archived document metadata" [metadata]="metadata.archive_metadata"></pngx-metadata-collapse>
             }
-          </div>
-
-        </div>
 
-        <ng-template #saveButtons>
-          <div class="btn-group ms-auto">
-            <ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
-              <button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
-              @if (hasNext()) {
-                <button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save &amp; next</button>
-              }
-              @if (!hasNext()) {
-                <button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save &amp; close</button>
-              }
-            </ng-container>
-            <button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
-          </div>
-        </ng-template>
+          </ng-template>
+        </li>
 
-        <ng-template #previewContent>
-          @if (!metadata) {
-            <div class="w-100 h-100 d-flex align-items-center justify-content-center">
-              <div>
-                <div class="spinner-border spinner-border-sm me-2" role="status"></div>
-                <ng-container i18n>Loading...</ng-container>
-              </div>
-            </div>
+        <li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
+          <a ngbNavLink i18n>Preview</a>
+          @if (!pdfPreview.offsetParent) {
+            <ng-template ngbNavContent>
+              <ng-container *ngTemplateOutlet="previewContent"></ng-container>
+            </ng-template>
           }
-          @if (getContentType() === 'application/pdf') {
-            @if (!useNativePdfViewer ) {
-              <div class="preview-sticky pdf-viewer-container">
-                <pngx-pdf-viewer
-                  [src]="{ url: previewUrl, password: password }"
-                  [original-size]="false"
-                  [show-borders]="true"
-                  [show-all]="true"
-                  [(page)]="previewCurrentPage"
-                  [zoom-scale]="previewZoomScale"
-                  [zoom]="previewZoomSetting"
-                  (error)="onError($event)"
-                  (after-load-complete)="pdfPreviewLoaded($event)">
-                </pngx-pdf-viewer>
+        </li>
+
+        @if (notesEnabled) {
+          <li [ngbNavItem]="DocumentDetailNavIDs.Notes">
+            <a class="text-nowrap" ngbNavLink i18n>Notes @if (document?.notes.length) {
+              <span class="badge text-bg-secondary ms-1">{{document.notes.length}}</span>
+            }</a>
+            <ng-template ngbNavContent>
+              <pngx-document-notes [documentId]="documentId" [notes]="document?.notes" [addDisabled]="!userCanEdit" (updated)="notesUpdated($event)"></pngx-document-notes>
+            </ng-template>
+          </li>
+        }
+
+        @if (showPermissions) {
+          <li [ngbNavItem]="DocumentDetailNavIDs.Permissions">
+            <a ngbNavLink i18n>Permissions</a>
+            <ng-template ngbNavContent>
+              <div class="mb-3">
+                <pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
               </div>
-            } @else {
-              <object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
-            }
-          }
-          @if (renderAsPlainText) {
-            <div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
-          }
-          @if (showPasswordField) {
-            <div class="password-prompt">
-              <form>
-                <input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
-              </form>
-            </div>
-          }
-        </ng-template>
+            </ng-template>
+          </li>
+        }
+      </ul>
+
+      <div [ngbNavOutlet]="nav" class="mt-3"></div>
+
+    </form>
+  </div>
+
+  <div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
+    <ng-container *ngTemplateOutlet="previewContent"></ng-container>
+    @if (renderAsPlainText) {
+      <div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
+    }
+    @if (requiresPassword) {
+      <div class="password-prompt">
+        <form>
+          <input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
+        </form>
+      </div>
+    }
+  </div>
+
+</div>
+
+<ng-template #saveButtons>
+  <div class="btn-group ms-auto">
+    <ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
+      <button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
+      @if (hasNext()) {
+        <button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save &amp; next</button>
+      }
+      @if (!hasNext()) {
+        <button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save &amp; close</button>
+      }
+    </ng-container>
+    <button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
+  </div>
+</ng-template>
+
+<ng-template #previewContent>
+  @if (!metadata) {
+    <div class="w-100 h-100 d-flex align-items-center justify-content-center">
+      <div>
+        <div class="spinner-border spinner-border-sm me-2" role="status"></div>
+        <ng-container i18n>Loading...</ng-container>
+      </div>
+    </div>
+  }
+  @if (getContentType() === 'application/pdf') {
+    @if (!useNativePdfViewer ) {
+      <div class="preview-sticky pdf-viewer-container">
+        <pngx-pdf-viewer
+          [src]="{ url: previewUrl, password: password }"
+          [original-size]="false"
+          [show-borders]="true"
+          [show-all]="true"
+          [(page)]="previewCurrentPage"
+          [zoom-scale]="previewZoomScale"
+          [zoom]="previewZoomSetting"
+          (error)="onError($event)"
+          (after-load-complete)="pdfPreviewLoaded($event)">
+        </pngx-pdf-viewer>
+      </div>
+    } @else {
+      <object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
+    }
+  }
+  @if (renderAsPlainText) {
+    <div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
+  }
+  @if (showPasswordField) {
+    <div class="password-prompt">
+      <form>
+        <input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
+      </form>
+    </div>
+  }
+</ng-template>
index 1492c3e0e3e942e80d54d9498733abdcbfea78fc..51a77fb91031b8fc00f8b992dc76dc829c0005a8 100644 (file)
@@ -255,9 +255,6 @@ describe('DocumentDetailComponent', () => {
 
     router = TestBed.inject(Router)
     activatedRoute = TestBed.inject(ActivatedRoute)
-    jest
-      .spyOn(activatedRoute, 'paramMap', 'get')
-      .mockReturnValue(of(convertToParamMap({ id: 3 })))
     openDocumentsService = TestBed.inject(OpenDocumentsService)
     documentService = TestBed.inject(DocumentService)
     modalService = TestBed.inject(NgbModal)
@@ -295,6 +292,17 @@ describe('DocumentDetailComponent', () => {
     expect(navigateSpy).toHaveBeenCalledWith(['documents', 3, 'notes'])
   })
 
+  it('should forward id without section to details', () => {
+    const navigateSpy = jest.spyOn(router, 'navigate')
+    jest
+      .spyOn(activatedRoute, 'paramMap', 'get')
+      .mockReturnValue(of(convertToParamMap({ id: 3 })))
+    fixture.detectChanges()
+    expect(navigateSpy).toHaveBeenCalledWith(['documents', 3, 'details'], {
+      replaceUrl: true,
+    })
+  })
+
   it('should update title after debounce', fakeAsync(() => {
     initNormally()
     component.titleInput.value = 'Foo Bar'
@@ -320,6 +328,7 @@ describe('DocumentDetailComponent', () => {
   })
 
   it('should load already-opened document via param', () => {
+    initNormally()
     jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
     jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
     jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
@@ -400,8 +409,11 @@ describe('DocumentDetailComponent', () => {
   })
 
   it('should 404 on invalid id', () => {
-    jest.spyOn(documentService, 'get').mockReturnValueOnce(of(null))
     const navigateSpy = jest.spyOn(router, 'navigate')
+    jest
+      .spyOn(activatedRoute, 'paramMap', 'get')
+      .mockReturnValue(of(convertToParamMap({ id: 999, section: 'details' })))
+    jest.spyOn(documentService, 'get').mockReturnValueOnce(of(null))
     fixture.detectChanges()
     expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true })
   })
@@ -936,11 +948,33 @@ describe('DocumentDetailComponent', () => {
     expect(refreshSpy).toHaveBeenCalled()
   })
 
+  it('should get suggestions', () => {
+    const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
+    suggestionsSpy.mockReturnValue(of({ tags: [1, 2] }))
+    initNormally()
+    expect(suggestionsSpy).toHaveBeenCalled()
+    expect(component.suggestions).toEqual({ tags: [1, 2] })
+  })
+
+  it('should show error if needed for get suggestions', () => {
+    const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
+    const errorSpy = jest.spyOn(toastService, 'showError')
+    suggestionsSpy.mockImplementationOnce(() =>
+      throwError(() => new Error('failed to get suggestions'))
+    )
+    initNormally()
+    expect(suggestionsSpy).toHaveBeenCalled()
+    expect(errorSpy).toHaveBeenCalled()
+  })
+
   it('should warn when open document does not match doc retrieved from backend on init', () => {
     const modalSpy = jest.spyOn(modalService, 'open')
     const openDoc = Object.assign({}, doc)
     // simulate a document being modified elsewhere and db updated
     doc.modified = new Date()
+    jest
+      .spyOn(activatedRoute, 'paramMap', 'get')
+      .mockReturnValue(of(convertToParamMap({ id: 3, section: 'details' })))
     jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
     jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(openDoc)
     jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
@@ -951,12 +985,13 @@ describe('DocumentDetailComponent', () => {
       })
     )
     fixture.detectChanges() // calls ngOnInit
-    expect(modalSpy).toHaveBeenCalledWith(ConfirmDialogComponent, {
-      backdrop: 'static',
-    })
+    expect(modalSpy).toHaveBeenCalledWith(ConfirmDialogComponent)
   })
 
   function initNormally() {
+    jest
+      .spyOn(activatedRoute, 'paramMap', 'get')
+      .mockReturnValue(of(convertToParamMap({ id: 3, section: 'details' })))
     jest
       .spyOn(documentService, 'get')
       .mockReturnValueOnce(of(Object.assign({}, doc)))
index 83057b8cd4ba127246642dc5aa5d7c72431b8c88..16eb9599c5c96328e39eebbb66b5d48e61c71be8 100644 (file)
@@ -33,6 +33,7 @@ import {
   map,
   debounceTime,
   distinctUntilChanged,
+  filter,
 } from 'rxjs/operators'
 import { DocumentSuggestions } from 'src/app/data/document-suggestions'
 import {
@@ -257,6 +258,13 @@ export class DocumentDetailComponent
 
     this.route.paramMap
       .pipe(
+        filter((paramMap) => {
+          // only init when changing docs & section is set
+          return (
+            +paramMap.get('id') !== this.documentId &&
+            paramMap.get('section')?.length > 0
+          )
+        }),
         takeUntil(this.unsubscribeNotifier),
         switchMap((paramMap) => {
           const documentId = +paramMap.get('id')
@@ -295,15 +303,12 @@ export class DocumentDetailComponent
               new Date(doc.modified) > new Date(openDocument.modified) &&
               !this.modalService.hasOpenModals()
             ) {
-              let modal = this.modalService.open(ConfirmDialogComponent, {
-                backdrop: 'static',
-              })
+              let modal = this.modalService.open(ConfirmDialogComponent)
               modal.componentInstance.title = $localize`Document changes detected`
               modal.componentInstance.messageBold = $localize`The version of this document in your browser session appears older than the existing version.`
               modal.componentInstance.message = $localize`Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.`
-              modal.componentInstance.cancelBtnCaption = $localize`Ok`
-              modal.componentInstance.cancelBtnClass = 'btn-primary'
-              modal.componentInstance.btnClass = 'visually-hidden'
+              modal.componentInstance.cancelBtnClass = 'visually-hidden'
+              modal.componentInstance.btnCaption = $localize`Ok`
             }
 
             if (this.documentForm.dirty) {
@@ -425,11 +430,14 @@ export class DocumentDetailComponent
   updateComponent(doc: Document) {
     this.document = doc
     this.requiresPassword = false
-    // this.customFields = doc.custom_fields.concat([])
     this.updateFormForCustomFields()
     this.documentsService
       .getMetadata(doc.id)
-      .pipe(first())
+      .pipe(
+        first(),
+        takeUntil(this.unsubscribeNotifier),
+        takeUntil(this.docChangeNotifier)
+      )
       .subscribe({
         next: (result) => {
           this.metadata = result
@@ -450,7 +458,11 @@ export class DocumentDetailComponent
     ) {
       this.documentsService
         .getSuggestions(doc.id)
-        .pipe(first(), takeUntil(this.unsubscribeNotifier))
+        .pipe(
+          first(),
+          takeUntil(this.unsubscribeNotifier),
+          takeUntil(this.docChangeNotifier)
+        )
         .subscribe({
           next: (result) => {
             this.suggestions = result